Compare commits
709 Commits
2024-10
...
temp-branc
| Author | SHA1 | Date | |
|---|---|---|---|
| 8caaf95427 | |||
| 81df650d09 | |||
| 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 | |||
|
|
a27eb7e462 | ||
|
|
f68d5da31e | ||
|
|
2d889b07f7 | ||
|
|
417a53a835 | ||
|
|
04a667531b | ||
| 9ecfe4f59b | |||
|
|
0d80e33d13 | ||
|
|
a2f7b1b9de | ||
| ee96b8e56b | |||
|
|
a0f6846f53 | ||
|
|
6c88c54982 | ||
|
|
a177d7991c | ||
| b2ff27b15c | |||
|
|
f8fb45cf6f | ||
| 35a47b2fa3 | |||
|
|
4c0816a829 | ||
|
|
1eb64fadae | ||
|
|
3ceb73d601 | ||
|
|
9d701ad55e | ||
| 8453b1da95 | |||
|
|
ba2fec5ac0 | ||
| 46a2e2068b | |||
| 7336a0aaa8 | |||
|
|
910069a463 | ||
| 56477157aa | |||
|
|
a67c5de69d | ||
|
|
95d5396759 | ||
| 858fc9aa05 | |||
| a6b0509179 | |||
|
|
55793dcecd | ||
| c9ac33864a | |||
| 6e4bbfc383 | |||
|
|
cb4d8d502a | ||
| 067f2235a7 | |||
| b42ede2853 | |||
| 9a4ef6abb5 | |||
|
|
af2b080ff5 | ||
|
|
0e056a7de1 | ||
|
|
b5e5286411 | ||
|
|
d01c0e14c0 | ||
|
|
0cf8bbc3d5 | ||
|
|
3a239b5123 | ||
|
|
0eabe977cb | ||
|
|
f1f86ea854 | ||
|
|
e048c92ad1 | ||
|
|
65f46a22f5 | ||
| 5d4cce365e | |||
|
|
b4c9bd12c9 | ||
|
|
8a11a9e5b1 | ||
|
|
b2738e1726 | ||
|
|
5d16d6c494 | ||
|
|
2693015dab | ||
|
|
3ecffc99b8 | ||
| 9c02a7cb8e | |||
|
|
a4edf8b3f6 | ||
| 8328d6c06e | |||
|
|
2ac33e8cec | ||
|
|
bbb911c917 | ||
|
|
bef9723255 | ||
|
|
47e3e5218d | ||
|
|
72800e1e44 | ||
| f6547b02a4 | |||
|
|
8fb22017d3 | ||
|
|
6f8070c85f | ||
| c09a0f4b85 | |||
| ba22f2b796 | |||
|
|
f62aa1f7f7 | ||
|
|
f19a467300 | ||
|
|
f4550b9c76 | ||
|
|
a5791ef76b | ||
|
|
dff4b556eb | ||
|
|
c013cd2eac | ||
| dc366ae1c3 | |||
| a9d2ba8dde | |||
|
|
de138328e9 | ||
|
|
757501efa8 | ||
|
|
73028c70d2 | ||
|
|
bdb3efd81d | ||
| 20423028f7 | |||
|
|
6442219087 | ||
|
|
a0148badaf | ||
| 8745f8bbbe | |||
|
|
277b1710fa | ||
| 1115520106 | |||
|
|
c8b63a7a7a | ||
|
|
1ed27ed29f | ||
|
|
072a2b6bfc | ||
|
|
b2bb7c3901 | ||
|
|
ad2752197a | ||
| 424a81ac53 | |||
|
|
38332616fa | ||
| 118068bafe | |||
| 8103b45a61 | |||
|
|
ef4c4a0a17 | ||
|
|
31a2311698 | ||
|
|
9937c3cb5f | ||
| 482d7f6ce5 | |||
|
|
5905c0b438 | ||
| 594ac7d4d4 | |||
| 9d2aad81e4 | |||
| 6b1303d250 | |||
| c45800f6d5 | |||
|
|
74cdfaf882 | ||
|
|
34ff162db1 | ||
|
|
bd6af21b99 | ||
| f0566a5969 | |||
| 76a571921a | |||
| 512ed2a542 | |||
|
|
c2c5c9668e | ||
|
|
a0b0023def | ||
| a11189d85c | |||
| ed54f79983 | |||
|
|
f03622ac5b | ||
|
|
bf3fe9450f | ||
| b4e2727c58 | |||
| 977ab4bf1b | |||
| d26504c841 | |||
| 418e17a427 | |||
| 4e0e50a4f8 | |||
| e05403bfa7 | |||
| b1a2ccd018 | |||
| 10f39b44a2 | |||
| 9eca074ffb | |||
| 9820d0a915 | |||
|
|
525de0c24c | ||
|
|
744c72dea2 | ||
|
|
b4114f19de | ||
|
|
b0dde94314 | ||
|
|
da7015fd89 | ||
|
|
9058c9081b | ||
|
|
34f317780e | ||
|
|
708b40880e | ||
|
|
bf661b5c1c | ||
|
|
57b08a99c6 | ||
| 8bc8a97e83 | |||
|
|
53548aec32 | ||
|
|
1380af6d50 | ||
|
|
21dcd87be2 | ||
|
|
fb97e421bb | ||
|
|
4f2ae176fa | ||
|
|
06e21711f2 | ||
|
|
95c564595f | ||
|
|
e725cd8a60 | ||
|
|
8507ad8378 | ||
|
|
cb8495fb0d | ||
|
|
8f8d3d38cc | ||
|
|
7eb80121a1 | ||
|
|
3736082096 | ||
|
|
2f303e63a3 | ||
|
|
ab2e5c346e | ||
|
|
ebb45238b0 | ||
|
|
6763721043 | ||
|
|
e235c3ef66 | ||
|
|
40b0e535cb | ||
|
|
9438301ebd | ||
|
|
bfed1b3e93 | ||
|
|
a9515f42b3 | ||
|
|
bbb62586f9 | ||
|
|
6aef21797d | ||
|
|
b7a90ebeb4 | ||
|
|
67665d3762 | ||
|
|
41252af4e9 | ||
|
|
2869f66154 | ||
|
|
2bdaabac6e | ||
|
|
4b41e2edd1 | ||
|
|
5fa09bb41b | ||
|
|
c96cadc5d1 | ||
|
|
b471a05181 | ||
|
|
e42a2c8309 | ||
| ea585a65ff | |||
|
|
59ba5c0a0c | ||
|
|
99d43041cb | ||
|
|
aa5f57a856 | ||
|
|
9fc0fbcbcf | ||
|
|
434dc676ce | ||
| ea83963d33 | |||
|
|
257df0a511 | ||
| 496cff6c11 | |||
|
|
484f9c9f4c | ||
|
|
19b69eed0e | ||
|
|
b165365203 | ||
|
|
a42dcd1d4b | ||
|
|
be3df5c497 | ||
|
|
7b951fa4b6 | ||
|
|
89a7b5b8f6 | ||
|
|
102aa504e6 | ||
| d1cbfcedcc | |||
| 116563f13e | |||
|
|
55bf90d0af | ||
|
|
7ac01b0f5e | ||
| 1a28d25026 | |||
|
|
92b0640f9c | ||
|
|
4a2fb595d0 | ||
| 56dd19b32e | |||
|
|
8460574f10 | ||
| 6f890daad6 | |||
|
|
6727dee61e | ||
|
|
3fcad5ff76 | ||
| 1fe7866970 | |||
|
|
b96d0cdeb1 | ||
| 0c30603f31 | |||
|
|
64bdba9fa2 | ||
|
|
c7a45b5500 | ||
| 42701db2d5 | |||
|
|
6c34c4a968 | ||
|
|
186d701dcb | ||
|
|
9b1b21dad5 | ||
|
|
00bda55ddd | ||
|
|
593b7e9d7b | ||
| f842886c00 | |||
|
|
af58306d96 | ||
|
|
09972e0764 | ||
|
|
7ba71562d8 | ||
|
|
71ba7fa139 | ||
|
|
48408859b1 | ||
|
|
fef01b692a | ||
|
|
55208ecf69 | ||
|
|
9a7d841d9c | ||
|
|
4ef5c982de | ||
|
|
22028fe7eb | ||
|
|
6ff3d7e336 | ||
| fe7af8a632 | |||
|
|
c67a2c8323 | ||
|
|
ce217b8b86 | ||
|
|
ebc52eb8ba | ||
|
|
0e1fb0e254 | ||
|
|
4d61cfc490 | ||
|
|
d0491b4850 | ||
|
|
610a3c1d13 | ||
|
|
ac0a9dd777 | ||
|
|
f294cb315f | ||
|
|
051e7fe14a | ||
|
|
38cbb22eff | ||
|
|
423fa581b4 | ||
|
|
279a8a6e83 | ||
|
|
a108b295d1 | ||
|
|
04bd8a7db3 | ||
|
|
d084950090 | ||
|
|
ff53c91279 | ||
|
|
751dc56568 | ||
| 7f42a21536 | |||
|
|
8ca804283a | ||
|
|
c43c5b4342 | ||
|
|
03ec551f71 | ||
|
|
0ddf98e1af | ||
|
|
1c014a75a7 | ||
|
|
c80ae2f95f | ||
|
|
d633b0bd35 | ||
|
|
81645ae1ba | ||
|
|
708a4e6fbd | ||
|
|
c60d41af10 | ||
|
|
91e2e9e7fe | ||
|
|
d1101cd157 | ||
|
|
7594657021 | ||
|
|
9bef1331f2 | ||
|
|
77e2a73345 | ||
|
|
1b518e508e | ||
|
|
1c12187d93 | ||
|
|
67b6f822c3 | ||
|
|
337797b0ed | ||
|
|
1075a72375 | ||
|
|
9fcf4e39a4 | ||
|
|
189ffad11d | ||
|
|
efccaae69e | ||
|
|
34d4321290 | ||
|
|
6cca428d62 | ||
|
|
653c556867 | ||
|
|
8c4ce5d8af | ||
|
|
2261a786a4 | ||
|
|
1f5916e898 | ||
|
|
fe9a81a3a3 | ||
|
|
c0466ed189 | ||
|
|
bc37e47b43 | ||
|
|
6968bf437d | ||
|
|
08be6c15fb | ||
|
|
1d7440b4f7 | ||
|
|
e0e1b6ea9e | ||
|
|
646e33d93a | ||
|
|
8419450427 | ||
|
|
ae94e35607 | ||
|
|
6fb6733727 | ||
|
|
273dd18405 | ||
|
|
8e78ae4e4c | ||
|
|
dda18320f8 | ||
|
|
993ac5f019 | ||
|
|
98dd7894b8 | ||
|
|
f34a996705 | ||
|
|
bc20fc0705 | ||
| 5d606673a6 | |||
|
|
a4f5034c7f | ||
|
|
6e6a6512c9 | ||
|
|
c7ac87c930 | ||
|
|
06b13fdfe1 | ||
|
|
a2ac240150 | ||
|
|
19e99fda9e | ||
|
|
bf94ed37d9 | ||
|
|
5ee33fabdf | ||
|
|
d65b68bbf4 | ||
|
|
6fb57f8422 | ||
|
|
cbbf2db4eb | ||
| 837ea9e369 | |||
|
|
496c7a7908 | ||
|
|
758b2a9033 | ||
|
|
913a86dcc7 | ||
|
|
84879b8d18 | ||
|
|
79b89e7e8b | ||
|
|
cb06b77aef | ||
|
|
b746a6445f | ||
|
|
c062e2a47f | ||
|
|
5828a52329 | ||
|
|
bb9a0f586f | ||
| 8cef3c8358 | |||
|
|
0c012152bb | ||
|
|
76ec26dca0 | ||
|
|
a5472781de | ||
|
|
ff7fbf92e6 | ||
| 158422860d | |||
| 422645bcfd | |||
|
|
dd96bc4171 | ||
|
|
2c950b3ac5 | ||
|
|
25e975fbc8 | ||
|
|
a006c0df33 | ||
|
|
b5cee74731 | ||
|
|
801e17e274 | ||
|
|
93d649162d | ||
| cc529b7e91 | |||
|
|
8f719e5a73 | ||
|
|
8f8788b43c | ||
|
|
04135f7044 | ||
|
|
aca953e827 | ||
|
|
de6922e9cb | ||
|
|
3e7bba6434 | ||
|
|
bd90965597 | ||
|
|
5b0cdb5c18 | ||
|
|
1e64382a98 | ||
|
|
9d6f8155fd | ||
|
|
2c70ae0696 | ||
|
|
92b34ac4c1 | ||
|
|
8bc0498b9f | ||
|
|
d2a8ddfbd2 | ||
|
|
1c69b3010b | ||
|
|
c045b2921c | ||
|
|
2def1546a9 | ||
|
|
776141bea7 | ||
|
|
ccb11142d1 | ||
|
|
1cc1a9ecf2 | ||
|
|
c374b4e438 | ||
|
|
30ae8aaffb | ||
|
|
2bb6a506de | ||
| 7ce35772ef | |||
| 0db2ebf4fc | |||
|
|
340dd6237e | ||
|
|
576beb10c8 | ||
|
|
ab3d90fd24 | ||
| 8a2ef975ea | |||
|
|
7da5d7c1f8 | ||
|
|
bd9f518241 | ||
|
|
7e4119771c | ||
|
|
45cac83b9b | ||
|
|
ba18a1310d | ||
|
|
1ad3fe48b4 | ||
| 413f668179 | |||
|
|
e3a19da34e | ||
|
|
491abdd18e | ||
|
|
1b3bc9f0f2 | ||
|
|
c9aaf82f3f | ||
|
|
1d87a607df | ||
| 6b33a40200 | |||
| a0e8e093fe | |||
| 12e805ccc1 | |||
| bdc3df38a3 | |||
| 6435d7135d | |||
|
|
99052f17f8 | ||
| 17f9ecee29 | |||
|
|
659f1e1ed5 | ||
|
|
dd9662091a | ||
|
|
dfa0daf70c | ||
|
|
61f7981ee5 | ||
|
|
8c75ecb09f | ||
|
|
de53e41f2c | ||
|
|
5b6ddacbf7 | ||
| 8d41510358 | |||
|
|
46948118dd | ||
| 4e0b16fad1 | |||
|
|
bab07585a9 | ||
|
|
ec2242bc35 | ||
| d58e957dae | |||
| 6ce4a97169 | |||
| ba301200b0 | |||
| 96c72acf72 | |||
| a25e1c7e15 | |||
| 7d4d925158 | |||
| 4f657669ad | |||
|
|
b6b1e9c9cb | ||
|
|
c571f870d8 | ||
| f3a0bb75ee | |||
| 1ee77361ff | |||
| 188b091f8d | |||
|
|
d41471f152 | ||
|
|
257dfa2221 | ||
| f4b7b3bdfe | |||
|
|
9c8ea385ed | ||
| ed3b91b2d8 | |||
|
|
c6d02d8724 | ||
| 2e9d640a75 | |||
|
|
d4dba1617d | ||
| 7163f387f6 | |||
| 3afa32bda4 | |||
| 4416459877 | |||
|
|
e7b0ce96f5 | ||
|
|
9fc1baad28 | ||
| dd4cf1eede | |||
| f5afc2e9bc | |||
| d44b511988 | |||
| f3a48ca345 | |||
|
|
3f6af65763 | ||
|
|
1572276448 | ||
|
|
8fa69a6876 | ||
| ec6e90e37a | |||
| 7054098b76 | |||
|
|
2a78f695dc | ||
|
|
cd82ea6c43 | ||
|
|
93973b6c6f | ||
|
|
f9c8c812ee | ||
| 9b6543bbb6 | |||
|
|
173aa100bd | ||
| 6bb50bdd01 | |||
| 54c909324c | |||
|
|
03c245758d | ||
| 9ca34607cc | |||
|
|
23d8bf8870 | ||
|
|
14fb79b1cc | ||
|
|
495b045e6d | ||
|
|
92887b773c | ||
|
|
db8205fbaa | ||
|
|
f971c3f2fa | ||
| fd74a00de9 | |||
| 534018b2f8 | |||
| f2fc74d27d | |||
|
|
ccb7bafda1 | ||
|
|
51539f1137 | ||
| ec5aae048d | |||
| d52c3e680b | |||
| e5eef6b1d1 | |||
|
|
e6e92a5503 | ||
| a415a410bf | |||
|
|
8d78cede68 | ||
| 07f1cd6f2f | |||
| 4e87c83556 | |||
| f2cfc383fd | |||
|
|
d91960700b | ||
| 55e7f1ce69 | |||
| 0b35ce08b0 | |||
|
|
cd78ad0588 | ||
| 12e1dc258a | |||
| 2b49a8b268 | |||
| 84fdd18f4e | |||
| ce3eb8f916 | |||
| e49ebf51ac | |||
|
|
bbbe729568 | ||
| 6de6c0955d | |||
| b24b282cbb | |||
| af47617d37 | |||
| 6bb8448db3 | |||
|
|
a0a42f8fa3 | ||
|
|
be232d390f | ||
| 28b8ca4ea7 | |||
| a5227c372f | |||
|
|
bf11f4c820 | ||
| ed69e53d69 | |||
|
|
a5e4baeb16 | ||
|
|
b13d34dcfc | ||
|
|
bb113720bc | ||
|
|
6040dee569 | ||
|
|
6b6ec07be2 | ||
|
|
83f5c10532 | ||
|
|
8cf4d04c65 | ||
|
|
2c7d465809 | ||
|
|
7bc1f768f9 | ||
|
|
a7b794b4d0 | ||
|
|
ea8a2bd8e7 | ||
|
|
949aa83539 | ||
|
|
11c95b6b42 | ||
| 38ffc6926d | |||
|
|
97c6e7a1e1 | ||
| 82acf16593 | |||
|
|
5c8a4fbfb8 | ||
|
|
8cfad7b57d | ||
|
|
74caf848c2 | ||
|
|
cf96b8ed83 | ||
|
|
3213bc9f84 | ||
| 3b6115159c | |||
|
|
1859f22109 | ||
|
|
f45a344db8 | ||
|
|
14f3ab52bb | ||
|
|
3bc797f23e | ||
|
|
d451763043 | ||
| dcd848984a | |||
| 487aeec04a | |||
| f589e2278a | |||
|
|
d43c38cd7c | ||
|
|
82afff2a6e | ||
|
|
7f5c132573 | ||
|
|
39f890dd4c | ||
|
|
1d8ceb15df | ||
|
|
68381da345 | ||
| 2277e015ae | |||
| 2b9cce41aa | |||
| 338e426017 | |||
|
|
6c9487a1e6 | ||
|
|
63831fc2d1 | ||
|
|
413bc98526 | ||
|
|
8c85e6c70b | ||
|
|
514f31a50a | ||
| 270b89ef2a | |||
|
|
705372f918 | ||
|
|
c4443c5669 | ||
| 9706c91c3f | |||
| 3a582d18d6 | |||
| 8dda33af0b | |||
|
|
5f694c88bf | ||
| 8f0b923744 | |||
| bc33f78d0c | |||
|
|
603d5cdd5f | ||
|
|
a81439b1d2 | ||
|
|
5dafcad3fa | ||
|
|
b0f54b0cde | ||
| d37d44123f | |||
|
|
8835249c1b | ||
|
|
5cc542e624 | ||
| e6d157e35f | |||
| 0e682e8d33 | |||
| 8760821aec | |||
|
|
7d88776633 | ||
|
|
1e7647dadc | ||
| b8a7f98cec | |||
| 97e009c84c | |||
|
|
786586e40c | ||
|
|
656fe2fbc3 | ||
| 2d5bb9694f | |||
|
|
e36652f930 | ||
| e9a294acdb | |||
| 3e90016ad5 | |||
| 5529093453 | |||
| 0232eee131 | |||
|
|
4c439e299c | ||
|
|
34e0f9f424 | ||
| 31a46b8df6 | |||
| 441d2fb39b | |||
| 1cdd1cbbe1 | |||
|
|
af9026b899 | ||
|
|
dbc07b2cf3 | ||
|
|
861f947499 | ||
| 8bc21dc861 | |||
|
|
532ad9fcb5 | ||
|
|
5fde9586fc | ||
|
|
878a2e68af | ||
|
|
e059e3c2af | ||
| 4f7bad232a | |||
|
|
bc0e93b522 | ||
| e432501e99 | |||
|
|
e48efef8d6 | ||
|
|
b801497a33 | ||
|
|
769e967815 | ||
|
|
379e4a33ff | ||
|
|
208ba4984b | ||
|
|
35ee76888d | ||
|
|
048021bb57 | ||
|
|
843d97e367 | ||
|
|
176a43d865 | ||
|
|
f287498659 | ||
|
|
3e34faed90 | ||
|
|
37aba2181b | ||
|
|
7335ffe445 | ||
|
|
43c1d56db3 | ||
|
|
2cffee4287 | ||
|
|
c125f20de4 | ||
|
|
f7ea59ce1b | ||
|
|
614849de64 | ||
|
|
d94c03cba1 | ||
|
|
44311fc6ec | ||
|
|
e4fa161e5a | ||
|
|
332c8909ff | ||
|
|
4c2c4d4ede | ||
|
|
c104834ea1 | ||
|
|
aa11314a07 | ||
|
|
8206a51c6c | ||
|
|
f4109275d7 | ||
|
|
f41595b727 | ||
|
|
f284b7a325 | ||
|
|
424b319c38 | ||
|
|
355ba48418 | ||
| 61b6b61a46 | |||
|
|
ea21d631c9 | ||
| 8f4483bbef | |||
|
|
2701816ebe | ||
| 8e75433282 | |||
| 53a4816272 | |||
|
|
b0db65a5cb | ||
|
|
4e3452a63c | ||
|
|
b546128183 | ||
|
|
2075d90760 | ||
|
|
63ac668df9 | ||
|
|
03e74ecb52 | ||
|
|
efebbc5305 | ||
|
|
442b80e7d9 | ||
|
|
712ebfaf24 | ||
|
|
42472dd496 | ||
|
|
b605605c13 | ||
|
|
81357906ca | ||
|
|
74b3e6a993 | ||
|
|
f3b561f8e9 | ||
|
|
b1e881798b | ||
| f0e82c458c | |||
|
|
05cdb20594 | ||
|
|
e640bd4516 | ||
|
|
bb5e5dfec5 | ||
| da38577e70 | |||
|
|
8896360473 | ||
| d0d21e84b8 |
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"
|
||||||
|
}
|
||||||
215
CLAUDE.md
Normal file
215
CLAUDE.md
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
CN_Gather是灿能公司的融合工具项目体,专门用于电能质量设备检测的企业级应用系统。采用Spring Boot多模块Maven架构,以detection模块为核心的检测业务系统。
|
||||||
|
|
||||||
|
## 项目架构
|
||||||
|
|
||||||
|
### 核心模块结构
|
||||||
|
- **entrance**: 应用入口模块,端口18092,整合所有其他模块
|
||||||
|
- **detection**: 核心检测业务模块,电能质量设备检测的完整业务流程
|
||||||
|
- **storage**: 数据存储模块,处理检测数据存储和谐波数据处理
|
||||||
|
- **system**: 基础系统模块,提供字典管理、日志管理、配置管理等基础功能
|
||||||
|
- **user**: 用户管理模块,处理认证授权和权限控制
|
||||||
|
|
||||||
|
### 模块依赖关系
|
||||||
|
```
|
||||||
|
entrance (启动入口)
|
||||||
|
├── system (基础服务层)
|
||||||
|
├── user (认证授权层)
|
||||||
|
├── detection (核心业务层) → 依赖 system, storage
|
||||||
|
└── storage (数据存储层) → 依赖 system
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常用开发命令
|
||||||
|
|
||||||
|
### 构建和打包
|
||||||
|
```bash
|
||||||
|
# 编译整个项目
|
||||||
|
mvn clean compile
|
||||||
|
|
||||||
|
# 打包所有模块
|
||||||
|
mvn clean package
|
||||||
|
|
||||||
|
# 跳过测试打包
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
|
||||||
|
# 安装到本地仓库
|
||||||
|
mvn clean install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行应用
|
||||||
|
```bash
|
||||||
|
# 运行主入口应用 (端口18092)
|
||||||
|
cd entrance
|
||||||
|
mvn spring-boot:run
|
||||||
|
|
||||||
|
# 运行事件智能模块 (独立应用)
|
||||||
|
cd event_smart
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
```bash
|
||||||
|
# 运行所有测试
|
||||||
|
mvn test
|
||||||
|
|
||||||
|
# 运行特定模块测试
|
||||||
|
cd detection
|
||||||
|
mvn test
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
### 核心框架
|
||||||
|
- **Spring Boot**: 2.3.12.RELEASE
|
||||||
|
- **MyBatis Plus**: 数据持久层框架
|
||||||
|
- **Maven**: 项目构建管理
|
||||||
|
- **Java**: 1.8
|
||||||
|
|
||||||
|
### 数据库
|
||||||
|
- **MySQL**: 主数据库 (192.168.1.24:13306/pqs9100)
|
||||||
|
- **Oracle**: event_smart模块使用
|
||||||
|
- **Druid**: 数据库连接池
|
||||||
|
|
||||||
|
### 通信技术
|
||||||
|
- **Netty**: Socket通信 (端口61000设备, 62000源)
|
||||||
|
- **WebSocket**: 实时数据推送 (端口7777)
|
||||||
|
- **RestTemplate**: HTTP客户端通信
|
||||||
|
|
||||||
|
### 其他关键技术
|
||||||
|
- **Apache POI + docx4j**: Word文档报告生成
|
||||||
|
- **FastJSON**: JSON数据处理
|
||||||
|
- **Spring Security + JWT**: 安全认证 (event_smart模块)
|
||||||
|
- **Redis**: 缓存服务 (event_smart模块)
|
||||||
|
|
||||||
|
## 关键配置
|
||||||
|
|
||||||
|
### 数据库配置
|
||||||
|
- 数据库URL: `jdbc:mysql://192.168.1.24:13306/pqs9100`
|
||||||
|
- MyBatis映射文件位置: `classpath*:com/njcn/**/mapping/*.xml`
|
||||||
|
- 主键生成策略: `assign_uuid`
|
||||||
|
|
||||||
|
### Socket通信配置
|
||||||
|
- 源设备Socket: 127.0.0.1:62000
|
||||||
|
- 被检设备Socket: 127.0.0.1:61000
|
||||||
|
- WebSocket端口: 7777
|
||||||
|
|
||||||
|
### 文件路径配置
|
||||||
|
- 日志目录: `D:\logs`
|
||||||
|
- 报告模板目录: `D:\template`
|
||||||
|
- 报告输出目录: `D:\report`
|
||||||
|
- Word模板位置: `entrance/src/main/resources/model/`
|
||||||
|
|
||||||
|
## detection模块核心架构
|
||||||
|
|
||||||
|
### 子模块功能划分
|
||||||
|
- **device**: 设备管理 - PqDev(被检设备)、PqStandardDev(标准设备)、PqDevSub(设备子表)
|
||||||
|
- **plan**: 检测计划管理 - AdPlan(检测计划)、AdPlanSource(计划源)、AdPlanStandardDev(计划标准设备)
|
||||||
|
- **script**: 检测脚本管理 - PqScript(检测脚本)、PqScriptDtls(脚本详情)、PqScriptCheckData(检测数据)
|
||||||
|
- **source**: 程控源管理 - PqSource(程控源设备)
|
||||||
|
- **err**: 误差体系管理 - PqErrSys(误差体系)、PqErrSysDtls(误差详情)
|
||||||
|
- **report**: 报告生成管理 - PqReport(报告模板),支持Word模板处理
|
||||||
|
- **monitor**: 监测管理 - PqMonitor(监测点管理)
|
||||||
|
- **icd**: ICD路径管理 - PqIcdPath(通信配置)
|
||||||
|
- **result**: 结果管理 - 检测结果查询和数据展示
|
||||||
|
- **type**: 设备类型管理 - DevType(设备类型字典)
|
||||||
|
|
||||||
|
### 核心检测流程 (PreDetectionController)
|
||||||
|
```java
|
||||||
|
// 主要检测接口
|
||||||
|
@PostMapping("/startPreTest") // 检测通用入口
|
||||||
|
@PostMapping("/ytxCheckSimulate") // 源通讯校验
|
||||||
|
@PostMapping("/startSimulateTest") // 启动程控源检测
|
||||||
|
@PostMapping("/coefficientCheck") // 系数校验
|
||||||
|
@PostMapping("/startContrastTest") // 比对检测
|
||||||
|
@PostMapping("/devPhaseSequence") // 设备相序检测
|
||||||
|
```
|
||||||
|
|
||||||
|
### Socket通信架构
|
||||||
|
- **SocketManager**: Socket会话管理,存储userId与Channel映射
|
||||||
|
- **WebServiceManager**: WebSocket服务管理,实时数据推送
|
||||||
|
- **通信处理器**:
|
||||||
|
- SocketSourceResponseService: 程控源响应处理
|
||||||
|
- SocketDevResponseService: 设备响应处理
|
||||||
|
- SocketContrastResponseService: 比对检测响应处理
|
||||||
|
- **通信工具**:
|
||||||
|
- CnSocketUtil: Socket连接工具
|
||||||
|
- FormalTestManager: 正式检测管理
|
||||||
|
- XiNumberManager: 系数管理
|
||||||
|
|
||||||
|
### 暂态检测参数
|
||||||
|
- 暂态前时间: 2秒
|
||||||
|
- 写入时间: 0.001秒
|
||||||
|
- 写出时间: 0.001秒
|
||||||
|
- 暂态后时间: 3秒
|
||||||
|
|
||||||
|
### 闪变参数
|
||||||
|
- 波形类型: CPM/SQU
|
||||||
|
- 占空比: 50%
|
||||||
|
|
||||||
|
## 开发注意事项
|
||||||
|
|
||||||
|
### detection模块包结构
|
||||||
|
```
|
||||||
|
detection/
|
||||||
|
├── controller/ # 控制层 - PreDetectionController
|
||||||
|
├── handler/ # Socket响应处理器
|
||||||
|
├── service/ # 业务逻辑层
|
||||||
|
│ └── impl/ # 服务实现
|
||||||
|
├── util/ # 工具类层
|
||||||
|
│ ├── business/ # 业务工具 - DetectionCommunicateUtil
|
||||||
|
│ └── socket/ # Socket通信工具
|
||||||
|
├── pojo/ # 数据模型层
|
||||||
|
│ ├── constant/ # 常量 - DetectionCommunicateConstant
|
||||||
|
│ ├── dto/ # 数据传输对象
|
||||||
|
│ ├── enums/ # 枚举 - DetectionCodeEnum等
|
||||||
|
│ ├── param/ # 请求参数
|
||||||
|
│ ├── po/ # 持久化对象
|
||||||
|
│ └── vo/ # 视图对象 - DetectionData等
|
||||||
|
└── [子模块]/ # device、plan、script等子模块
|
||||||
|
├── controller/ # 子模块控制器
|
||||||
|
├── service/ # 子模块服务
|
||||||
|
├── mapper/ # 数据访问层
|
||||||
|
│ └── mapping/ # MyBatis映射文件
|
||||||
|
└── pojo/ # 子模块数据对象
|
||||||
|
```
|
||||||
|
|
||||||
|
### 检测数据处理机制
|
||||||
|
- **任意值**: 取第一个满足条件的数据
|
||||||
|
- **部分值**: 去除最大最小值后取值
|
||||||
|
- **所有值**: 要求所有数据都合格
|
||||||
|
- **CP95值**: 取95%分位数
|
||||||
|
- **平均值**: 取算术平均值
|
||||||
|
|
||||||
|
### 检测项目类型
|
||||||
|
- **频率**: FREQ
|
||||||
|
- **电压**: V_RELATIVE(相对值)/V_ABSOLUTELY(绝对值)
|
||||||
|
- **电流**: I_RELATIVE/I_ABSOLUTELY
|
||||||
|
- **谐波**: HV/HI (2-50次谐波)
|
||||||
|
- **间谐波**: HSV/HSI
|
||||||
|
- **不平衡度**: IMBV/IMBA (三相不平衡)
|
||||||
|
- **闪变**: F (PST)
|
||||||
|
- **暂态**: VOLTAGE_MAG/VOLTAGE_DUR
|
||||||
|
|
||||||
|
### 检测模式
|
||||||
|
- **数字式检测**: 数字接口通信
|
||||||
|
- **模拟式检测**: 模拟信号输出
|
||||||
|
- **比对式检测**: 多台设备比对
|
||||||
|
|
||||||
|
### 报告生成机制
|
||||||
|
- **模板处理**: 使用POI和docx4j处理Word文档
|
||||||
|
- **模板位置**: `entrance/src/main/resources/model/`
|
||||||
|
- **支持模板**: NPQS-580、PQV-700、njcn_882系列等
|
||||||
|
- **功能**: 书签替换、表格填充、文档合并
|
||||||
|
- **云端上传**: 支持FTP批量上传报告
|
||||||
|
|
||||||
|
### 依赖组件
|
||||||
|
项目使用灿能公司自研组件:
|
||||||
|
- `njcn-common`: 通用工具包
|
||||||
|
- `mybatis-plus`: MyBatis增强包
|
||||||
|
- `spingboot2.3.12`: Spring Boot定制包
|
||||||
|
- `RestTemplate-plugin`: HTTP客户端插件
|
||||||
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
238
Gitea本地协作开发服务器配置指南.md
Normal file
238
Gitea本地协作开发服务器配置指南.md
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
# Gitea本地协作开发服务器配置指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本文档说明如何将本地安装的Gitea配置为团队协作开发服务器,替代原有的物理服务器环境。
|
||||||
|
|
||||||
|
## 1. 网络配置
|
||||||
|
|
||||||
|
### 1.1 确认本机IP地址
|
||||||
|
```bash
|
||||||
|
# Windows系统
|
||||||
|
ipconfig
|
||||||
|
# 查找本机局域网IP地址,通常形如 192.168.x.x 或 10.x.x.x
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 配置Gitea服务地址
|
||||||
|
编辑Gitea配置文件 `app.ini`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[server]
|
||||||
|
# 将localhost改为本机IP地址,确保同事可以访问
|
||||||
|
HTTP_ADDR = 0.0.0.0
|
||||||
|
HTTP_PORT = 3000
|
||||||
|
# 外部访问URL,替换为你的实际IP
|
||||||
|
ROOT_URL = http://192.168.x.x:3000/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 防火墙配置
|
||||||
|
确保Windows防火墙允许Gitea端口通信:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 打开Windows防火墙入站规则
|
||||||
|
# 添加端口3000的TCP入站规则
|
||||||
|
```
|
||||||
|
|
||||||
|
或在Windows防火墙中:
|
||||||
|
- 控制面板 → 系统和安全 → Windows Defender防火墙 → 高级设置
|
||||||
|
- 入站规则 → 新建规则 → 端口 → TCP → 特定本地端口: 3000
|
||||||
|
|
||||||
|
## 2. Gitea服务配置
|
||||||
|
|
||||||
|
### 2.1 启动Gitea服务
|
||||||
|
```bash
|
||||||
|
# 进入Gitea安装目录
|
||||||
|
cd C:\gitea # 或你的安装路径
|
||||||
|
gitea.exe web
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 配置为Windows服务(推荐)
|
||||||
|
创建Windows服务确保开机自启:
|
||||||
|
|
||||||
|
1. 下载NSSM (Non-Sucking Service Manager)
|
||||||
|
2. 以管理员身份运行命令提示符:
|
||||||
|
```bash
|
||||||
|
nssm install Gitea
|
||||||
|
# 在弹出界面中配置:
|
||||||
|
# Path: C:\gitea\gitea.exe
|
||||||
|
# Arguments: web
|
||||||
|
# Working directory: C:\gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 启动服务:
|
||||||
|
```bash
|
||||||
|
net start Gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 数据库配置优化
|
||||||
|
如果使用SQLite(默认),确保数据文件路径正确:
|
||||||
|
```ini
|
||||||
|
[database]
|
||||||
|
DB_TYPE = sqlite3
|
||||||
|
PATH = data/gitea.db
|
||||||
|
```
|
||||||
|
|
||||||
|
如果需要更好性能,考虑配置MySQL:
|
||||||
|
```ini
|
||||||
|
[database]
|
||||||
|
DB_TYPE = mysql
|
||||||
|
HOST = 127.0.0.1:3306
|
||||||
|
NAME = gitea
|
||||||
|
USER = gitea
|
||||||
|
PASSWD = your_password
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 同事访问配置
|
||||||
|
|
||||||
|
### 3.1 提供访问地址
|
||||||
|
向同事提供访问地址:
|
||||||
|
```
|
||||||
|
http://你的IP地址:3000
|
||||||
|
例如: http://192.168.1.100:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 用户账号管理
|
||||||
|
1. 访问管理界面创建用户账号
|
||||||
|
2. 或开启用户自注册:
|
||||||
|
```ini
|
||||||
|
[service]
|
||||||
|
DISABLE_REGISTRATION = false
|
||||||
|
REQUIRE_SIGNIN_VIEW = false
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 权限配置
|
||||||
|
为协作项目设置适当权限:
|
||||||
|
- 项目所有者:完全控制权限
|
||||||
|
- 协作者:推送/拉取权限
|
||||||
|
- 读者:仅读取权限
|
||||||
|
|
||||||
|
## 4. 代码仓库迁移
|
||||||
|
|
||||||
|
### 4.1 从原服务器迁移仓库
|
||||||
|
如果原服务器数据可恢复:
|
||||||
|
```bash
|
||||||
|
# 在原服务器或备份中找到Git裸仓库
|
||||||
|
# 复制到新Gitea的repositories目录
|
||||||
|
# 通常位于 gitea-repositories/用户名/仓库名.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 重新创建仓库
|
||||||
|
如果需要重新创建:
|
||||||
|
1. 在Gitea界面创建新仓库
|
||||||
|
2. 本地添加新的远程地址:
|
||||||
|
```bash
|
||||||
|
git remote remove origin
|
||||||
|
git remote add origin http://你的IP:3000/用户名/仓库名.git
|
||||||
|
git push -u origin master
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 开发工作流配置
|
||||||
|
|
||||||
|
### 5.1 分支保护规则
|
||||||
|
为主要分支设置保护规则:
|
||||||
|
- 设置 → 分支 → 分支保护规则
|
||||||
|
- 保护master分支,要求代码审查
|
||||||
|
|
||||||
|
### 5.2 Webhook配置
|
||||||
|
如果需要CI/CD集成:
|
||||||
|
```
|
||||||
|
设置 → Webhooks → 添加Webhook
|
||||||
|
配置自动构建触发器
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 备份策略
|
||||||
|
|
||||||
|
### 6.1 定期备份
|
||||||
|
```bash
|
||||||
|
# 备份Gitea数据目录
|
||||||
|
# 包括:repositories/, data/, log/, custom/
|
||||||
|
robocopy "C:\gitea" "D:\backup\gitea" /MIR /Z /R:3 /W:10
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 自动备份脚本
|
||||||
|
创建批处理文件实现定期备份:
|
||||||
|
```batch
|
||||||
|
@echo off
|
||||||
|
set BACKUP_DIR=D:\backup\gitea_%date:~0,4%%date:~5,2%%date:~8,2%
|
||||||
|
robocopy "C:\gitea" "%BACKUP_DIR%" /MIR /Z /R:3 /W:10
|
||||||
|
echo Backup completed to %BACKUP_DIR%
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 常见问题排查
|
||||||
|
|
||||||
|
### 7.1 访问问题
|
||||||
|
- 检查防火墙设置
|
||||||
|
- 确认IP地址和端口正确
|
||||||
|
- 验证Gitea服务是否正常运行
|
||||||
|
|
||||||
|
### 7.2 权限问题
|
||||||
|
- 检查用户账号状态
|
||||||
|
- 确认仓库权限设置
|
||||||
|
- 验证SSH密钥配置(如使用SSH)
|
||||||
|
|
||||||
|
### 7.3 性能优化
|
||||||
|
```ini
|
||||||
|
[server]
|
||||||
|
# 调整并发连接数
|
||||||
|
HTTP_ADDR = 0.0.0.0
|
||||||
|
HTTP_PORT = 3000
|
||||||
|
|
||||||
|
[database]
|
||||||
|
# 数据库连接池配置
|
||||||
|
MAX_IDLE_CONNS = 30
|
||||||
|
MAX_OPEN_CONNS = 300
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 安全建议
|
||||||
|
|
||||||
|
1. **网络安全**:
|
||||||
|
- 仅在受信任的局域网环境中开放
|
||||||
|
- 考虑使用VPN访问
|
||||||
|
- 定期更新Gitea版本
|
||||||
|
|
||||||
|
2. **访问控制**:
|
||||||
|
- 禁用不必要的公开注册
|
||||||
|
- 使用强密码策略
|
||||||
|
- 启用双因子认证
|
||||||
|
|
||||||
|
3. **数据安全**:
|
||||||
|
- 定期备份重要数据
|
||||||
|
- 监控异常访问
|
||||||
|
- 记录操作日志
|
||||||
|
|
||||||
|
## 9. 同事操作指南
|
||||||
|
|
||||||
|
### 9.1 首次设置
|
||||||
|
```bash
|
||||||
|
# 克隆仓库
|
||||||
|
git clone http://你的IP:3000/用户名/CN_Gather.git
|
||||||
|
|
||||||
|
# 配置用户信息
|
||||||
|
git config user.name "姓名"
|
||||||
|
git config user.email "邮箱"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 日常协作
|
||||||
|
```bash
|
||||||
|
# 拉取最新代码
|
||||||
|
git pull origin master
|
||||||
|
|
||||||
|
# 创建功能分支
|
||||||
|
git checkout -b feature/新功能
|
||||||
|
|
||||||
|
# 提交更改
|
||||||
|
git add .
|
||||||
|
git commit -m "描述信息"
|
||||||
|
git push origin feature/新功能
|
||||||
|
|
||||||
|
# 在Gitea界面创建Pull Request
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**联系信息**:
|
||||||
|
- Gitea服务地址:http://你的IP:3000
|
||||||
|
- 管理员:[你的联系方式]
|
||||||
|
- 紧急联系:[备用联系方式]
|
||||||
|
|
||||||
|
**注意**:请确保定期备份重要代码,避免数据丢失。
|
||||||
9
detection/Readme.md
Normal file
9
detection/Readme.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#### 简介
|
||||||
|
设备模块主要包含以下功能:
|
||||||
|
* 检测计划管理
|
||||||
|
* 被检设备管理
|
||||||
|
* 被检设备下监测点管理
|
||||||
|
* 误差体系管理
|
||||||
|
* 检测脚本管理
|
||||||
|
* 检测源管理
|
||||||
|
* 检测报告管理
|
||||||
145
detection/pom.xml
Normal file
145
detection/pom.xml
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.njcn.gather</groupId>
|
||||||
|
<artifactId>CN_Gather</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>detection</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn</groupId>
|
||||||
|
<artifactId>njcn-common</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn</groupId>
|
||||||
|
<artifactId>mybatis-plus</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn</groupId>
|
||||||
|
<artifactId>spingboot2.3.12</artifactId>
|
||||||
|
<version>2.3.12</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn.gather</groupId>
|
||||||
|
<artifactId>system</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>1.2.83</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn.gather</groupId>
|
||||||
|
<artifactId>storage</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-all</artifactId>
|
||||||
|
<version>4.1.68.Final</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- WordToHtml .doc .odcx poi -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi-scratchpad</artifactId>
|
||||||
|
<version>4.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- 操作excel的库 注意版本保持一致 poi poi-ooxml poi-scratchpad -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi</artifactId>
|
||||||
|
<version>4.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<!--poi-ooxml和*poi-ooxml-schemas*是poi对2007及以上版本的扩充。-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi-ooxml-schemas</artifactId>
|
||||||
|
<version>4.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi-ooxml</artifactId>
|
||||||
|
<version>4.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.docx4j/docx4j -->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.docx4j</groupId>-->
|
||||||
|
<!-- <artifactId>docx4j-core</artifactId>-->
|
||||||
|
<!-- <version>8.3.8</version>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.xml.bind</groupId>
|
||||||
|
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||||
|
<version>2.3.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jaxb</groupId>
|
||||||
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
|
<version>2.3.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.docx4j</groupId>
|
||||||
|
<artifactId>docx4j</artifactId>
|
||||||
|
<version>6.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>2.12.0</version> <!-- 您可以根据需要选择其他版本 -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-core</artifactId>
|
||||||
|
<version>2.12.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-annotations</artifactId>
|
||||||
|
<version>2.12.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn</groupId>
|
||||||
|
<artifactId>RestTemplate-plugin</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-net</groupId>
|
||||||
|
<artifactId>commons-net</artifactId>
|
||||||
|
<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>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
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;
|
||||||
|
import com.njcn.gather.detection.service.PreDetectionService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chendaofei
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Api(tags = "预检测")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/prepare")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PreDetectionController extends BaseController {
|
||||||
|
|
||||||
|
private final PreDetectionService preDetectionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始检测通用入口
|
||||||
|
*
|
||||||
|
* @param param 实体参数
|
||||||
|
*/
|
||||||
|
@PostMapping("/startPreTest")
|
||||||
|
@OperateInfo
|
||||||
|
@ApiOperation("开始检测")
|
||||||
|
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
|
||||||
|
public HttpResult<String> startPreTest(@RequestBody @Validated PreDetectionParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("startPreTest");
|
||||||
|
preDetectionService.sourceCommunicationCheck(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 程控源-源通讯校验
|
||||||
|
*
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/ytxCheckSimulate")
|
||||||
|
@OperateInfo
|
||||||
|
@ApiOperation("源通讯校验")
|
||||||
|
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
|
||||||
|
public HttpResult<String> ytxCheckSimulate(@RequestBody @Validated SimulateDetectionParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("ytxCheckSimulate");
|
||||||
|
preDetectionService.ytxCheckSimulate(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 程控源检测
|
||||||
|
*/
|
||||||
|
@PostMapping("/startSimulateTest")
|
||||||
|
@OperateInfo
|
||||||
|
@ApiOperation("启动")
|
||||||
|
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
|
||||||
|
public HttpResult<String> startTestSimulate(@RequestBody @Validated SimulateDetectionParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("startTestSimulate");
|
||||||
|
preDetectionService.sendScript(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭检测-程控源
|
||||||
|
*/
|
||||||
|
@PostMapping("/closeSimulateTest")
|
||||||
|
@OperateInfo
|
||||||
|
@ApiOperation("停止")
|
||||||
|
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
|
||||||
|
public HttpResult<String> closeSimulateTest(@RequestBody @Validated SimulateDetectionParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("closeSimulateTest");
|
||||||
|
preDetectionService.closeTestSimulate(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系数校验
|
||||||
|
*/
|
||||||
|
@PostMapping("/coefficientCheck")
|
||||||
|
@OperateInfo
|
||||||
|
@ApiOperation("系数校验")
|
||||||
|
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
|
||||||
|
public HttpResult<String> coefficientCheck(@RequestBody PreDetectionParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("coefficientCheck");
|
||||||
|
preDetectionService.coefficientCheck(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂停检测
|
||||||
|
*/
|
||||||
|
@GetMapping("/closePreTest")
|
||||||
|
@OperateInfo
|
||||||
|
@ApiOperation("暂停检测")
|
||||||
|
@ApiImplicitParam(name = "param", value = "参数", required = true)
|
||||||
|
public HttpResult<String> temStopTest() {
|
||||||
|
String methodDescribe = getMethodDescribe("temStopTest");
|
||||||
|
preDetectionService.temStopTest();
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新开始检测
|
||||||
|
*/
|
||||||
|
@PostMapping("/restartTemTest")
|
||||||
|
@OperateInfo
|
||||||
|
@ApiOperation("重新开始检测")
|
||||||
|
@ApiImplicitParam(name = "param", value = "参数", required = true)
|
||||||
|
public HttpResult<String> restartTemTest(@RequestBody PreDetectionParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("restartTemTest");
|
||||||
|
preDetectionService.restartTemTest(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始比对检测(包括预检测、正式检测)通用入口
|
||||||
|
*/
|
||||||
|
@PostMapping("/startContrastTest")
|
||||||
|
@OperateInfo
|
||||||
|
@ApiOperation("开始比对检测")
|
||||||
|
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
|
||||||
|
public HttpResult<String> startContrastTest(@RequestBody @Validated ContrastDetectionParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("startContrastTest");
|
||||||
|
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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,658 @@
|
|||||||
|
package com.njcn.gather.detection.handler;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||||
|
import com.njcn.gather.detection.pojo.enums.SourceResponseCodeEnum;
|
||||||
|
import com.njcn.gather.detection.pojo.param.DevPhaseSequenceParam;
|
||||||
|
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||||
|
import com.njcn.gather.detection.pojo.vo.DevLineTestResult;
|
||||||
|
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.websocket.WebServiceManager;
|
||||||
|
import com.njcn.gather.device.pojo.vo.PreDetection;
|
||||||
|
import com.njcn.gather.device.service.IPqDevService;
|
||||||
|
import com.njcn.gather.script.pojo.po.SourceIssue;
|
||||||
|
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.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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备信息服务,提供设备基础信息查询功能
|
||||||
|
*/
|
||||||
|
private final IPqDevService iPqDevService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket连接管理器,负责管理设备和源的Socket连接
|
||||||
|
*/
|
||||||
|
private final SocketManager socketManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送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 {
|
||||||
|
// 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()) {
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||||
|
}
|
||||||
|
log.info("模拟检测源初始化成功,用户: {}, WebSocket发送: {}",
|
||||||
|
param.getUserPageId(), param.getSendWebMsg());
|
||||||
|
break;
|
||||||
|
case UNPROCESSED_BUSINESS:
|
||||||
|
// 业务暂未处理:直接转发消息给前端等待处理
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||||
|
break;
|
||||||
|
// 各种错误情况:源连接错误、程控源控制错误、测试项解析错误等
|
||||||
|
case SOURCE_CONNECTION_ERROR:
|
||||||
|
case CONTROLLED_SOURCE_ERROR:
|
||||||
|
case TEST_ITEM_PARSING_ERROR:
|
||||||
|
case SOURCE_CONTROL_ERROR:
|
||||||
|
case TARGET_SOURCE_ERROR:
|
||||||
|
case NOT_INITIALIZED:
|
||||||
|
case UNKNOWN_ERROR:
|
||||||
|
case UNABLE_TO_RESPOND:
|
||||||
|
// 所有错误情况统一处理:退出源连接并通知前端
|
||||||
|
CnSocketUtil.quitSendSource(param);
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// 未识别的响应码:发送通用错误消息
|
||||||
|
WebServiceManager.sendUnknownErrorMessage(param.getUserPageId());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理模拟检测时,通信模块返回报文
|
||||||
|
*
|
||||||
|
* @param param
|
||||||
|
* @param socketDataMsg
|
||||||
|
*/
|
||||||
|
private void handleSimulateTest(PreDetectionParam param, SocketDataMsg socketDataMsg) {
|
||||||
|
SourceResponseCodeEnum dictDataEnumByCode = SourceResponseCodeEnum.getDictDataEnumByCode(socketDataMsg.getCode());
|
||||||
|
if (ObjectUtil.isNotNull(dictDataEnumByCode)) {
|
||||||
|
switch (dictDataEnumByCode) {
|
||||||
|
case SUCCESS:
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||||
|
log.info("模拟检测源成功执行脚本,用户: {}, 响应消息: {}",
|
||||||
|
param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||||
|
break;
|
||||||
|
case UNPROCESSED_BUSINESS:
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sendErrorAndQuit(param, socketDataMsg, dictDataEnumByCode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系数校验源数据返回处理
|
||||||
|
* <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());
|
||||||
|
if (ObjectUtil.isNotNull(dictDataEnumByCode)) {
|
||||||
|
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||||
|
switch (dictDataEnumByCode) {
|
||||||
|
case SUCCESS:
|
||||||
|
//向前端推送信息
|
||||||
|
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"));
|
||||||
|
// 读取3次数据用于系数计算
|
||||||
|
phaseSequenceParam.setReadCount(3);
|
||||||
|
// 忽略前4次数据,等待测量稳定
|
||||||
|
phaseSequenceParam.setIgnoreCount(4);
|
||||||
|
socketMsg.setData(JSON.toJSONString(phaseSequenceParam));
|
||||||
|
SocketManager.sendMsg(s, JSON.toJSONString(socketMsg));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case UNPROCESSED_BUSINESS:
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sendErrorAndQuit(param, socketDataMsg, dictDataEnumByCode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置检测(当源初始化成功后,直接向装置通道向装置服务器发送,装置检测)
|
||||||
|
*
|
||||||
|
* @param param 参数
|
||||||
|
* @param socketDataMsg 消息
|
||||||
|
*/
|
||||||
|
private void detectionDev(PreDetectionParam param, SocketDataMsg socketDataMsg) {
|
||||||
|
SourceResponseCodeEnum dictDataEnumByCode = SourceResponseCodeEnum.getDictDataEnumByCode(socketDataMsg.getCode());
|
||||||
|
if (ObjectUtil.isNotNull(dictDataEnumByCode)) {
|
||||||
|
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||||
|
switch (dictDataEnumByCode) {
|
||||||
|
case SUCCESS:
|
||||||
|
//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:
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||||
|
break;
|
||||||
|
case SOURCE_CONNECTION_ERROR:
|
||||||
|
case CONTROLLED_SOURCE_ERROR:
|
||||||
|
case TEST_ITEM_PARSING_ERROR:
|
||||||
|
case SOURCE_CONTROL_ERROR:
|
||||||
|
case TARGET_SOURCE_ERROR:
|
||||||
|
case NOT_INITIALIZED:
|
||||||
|
case UNKNOWN_ERROR:
|
||||||
|
case UNABLE_TO_RESPOND:
|
||||||
|
CnSocketUtil.quitSendSource(param);
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// todo... 这种情况是报文的状态码不一致,需要记录到日志表,以便问题追踪
|
||||||
|
WebServiceManager.sendUnknownErrorMessage(param.getUserPageId());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// todo... 这种情况是报文的状态码不一致,需要记录到日志表,以便问题追踪
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相序检测向装置发送(当装置初始成功后,会向源发送加量请求。收到加量请求成功后会向装置发送参数下发。)
|
||||||
|
*
|
||||||
|
* @param param
|
||||||
|
* @param socketDataMsg
|
||||||
|
*/
|
||||||
|
private void phaseSequenceDev(PreDetectionParam param, SocketDataMsg socketDataMsg) {
|
||||||
|
SourceResponseCodeEnum dictDataEnumByCode = SourceResponseCodeEnum.getDictDataEnumByCode(socketDataMsg.getCode());
|
||||||
|
if (ObjectUtil.isNotNull(dictDataEnumByCode)) {
|
||||||
|
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||||
|
switch (dictDataEnumByCode) {
|
||||||
|
case SUCCESS:
|
||||||
|
//向前端推送信息
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||||
|
|
||||||
|
String s = param.getUserPageId() + CnSocketUtil.DEV_TAG;
|
||||||
|
socketMsg.setRequestId(SourceOperateCodeEnum.YJC_XUJY.getValue());
|
||||||
|
socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_DATA_REQUEST_02.getValue());
|
||||||
|
List<PreDetection> pqDevList = iPqDevService.getDevInfo(param.getDevIds());
|
||||||
|
List<String> moniterIdList = pqDevList.stream().flatMap(x -> x.getMonitorList().stream())
|
||||||
|
.map(PreDetection.MonitorListDTO::getLineId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
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:
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||||
|
break;
|
||||||
|
case MESSAGE_PARSING_ERROR:
|
||||||
|
CnSocketUtil.quitSendSource(param);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sendErrorAndQuit(param, socketDataMsg, dictDataEnumByCode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正式检测时向设备发送参数请求
|
||||||
|
* <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 程控源成功响应消息
|
||||||
|
*/
|
||||||
|
private void senParamToDev(PreDetectionParam param, SocketDataMsg socketDataMsg) {
|
||||||
|
SourceResponseCodeEnum dictDataEnumByCode = SourceResponseCodeEnum.getDictDataEnumByCode(socketDataMsg.getCode());
|
||||||
|
if (ObjectUtil.isNotNull(dictDataEnumByCode)) {
|
||||||
|
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||||
|
switch (dictDataEnumByCode) {
|
||||||
|
case SUCCESS:
|
||||||
|
//向前端推送信息
|
||||||
|
// webSocketHandler.sendMsgToUser(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||||
|
|
||||||
|
// 构造设备通道标识:用户ID + 设备标签
|
||||||
|
String s = param.getUserPageId() + CnSocketUtil.DEV_TAG;
|
||||||
|
|
||||||
|
// 获取当前检测的源脚本信息,包含检测类型和数据要求
|
||||||
|
SourceIssue sourceIssue = SocketManager.getSourceList().get(0);
|
||||||
|
// 数据类型列表,格式: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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("检测设备列表状态检查,用户: {}, 设备列表为空: {}",
|
||||||
|
param.getUserPageId(), CollectionUtils.isEmpty(devList));
|
||||||
|
|
||||||
|
// 构造设备数据请求参数
|
||||||
|
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();
|
||||||
|
devLineTestResult.setDeviceId(item.getDevId());
|
||||||
|
devLineTestResult.setDeviceName(item.getDevName());
|
||||||
|
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);
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), webSocketVO);
|
||||||
|
break;
|
||||||
|
case UNPROCESSED_BUSINESS:
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sendErrorAndQuit(param, socketDataMsg, dictDataEnumByCode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理退出检测的响应
|
||||||
|
* <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);
|
||||||
|
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||||
|
break;
|
||||||
|
case UNPROCESSED_BUSINESS:
|
||||||
|
break;
|
||||||
|
case MESSAGE_PARSING_ERROR:
|
||||||
|
SocketManager.removeUser(param.getUserPageId() + CnSocketUtil.SOURCE_TAG);
|
||||||
|
break;
|
||||||
|
case UNABLE_TO_RESPOND:
|
||||||
|
SocketManager.removeUser(param.getUserPageId() + CnSocketUtil.SOURCE_TAG);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
CnSocketUtil.quitSendSource(param);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化检测设备和监测点列表
|
||||||
|
* <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());
|
||||||
|
|
||||||
|
// 提取所有设备的监测点线路ID
|
||||||
|
this.monitorIdList = devList.stream()
|
||||||
|
.flatMap(x -> x.getMonitorList().stream())
|
||||||
|
.map(PreDetection.MonitorListDTO::getLineId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 同步更新系数管理器中的设备列表
|
||||||
|
XiNumberManager.xiDevList = devList;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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,22 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通信相关的常量
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @version 1.0
|
||||||
|
* @data 2025/4/15 14:11
|
||||||
|
*/
|
||||||
|
public interface DetectionCommunicateConstant {
|
||||||
|
|
||||||
|
String SOURCE_CHANNEL_NAME = "AUTO_DETECTION_SOURCE";
|
||||||
|
|
||||||
|
String DEVICE_CHANNEL_NAME = "AUTO_DETECTION_DEV";
|
||||||
|
|
||||||
|
String DEV = "_Dev";
|
||||||
|
|
||||||
|
String SOURCE = "_Source";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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,65 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.dto;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: cdf
|
||||||
|
* @CreateTime: 2025-01-04
|
||||||
|
* @Description: 给装置下发的系数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DevXiNumData {
|
||||||
|
|
||||||
|
private String devIP;
|
||||||
|
|
||||||
|
private Integer chnNum;
|
||||||
|
|
||||||
|
private List<GF> gf;
|
||||||
|
|
||||||
|
private Integer resultFlag;
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class GF{
|
||||||
|
|
||||||
|
private Integer uMonitorPoint;
|
||||||
|
|
||||||
|
private F f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class F{
|
||||||
|
|
||||||
|
private Integer count;
|
||||||
|
|
||||||
|
@JSONField(name = "Ua_gain", ordinal = 1)
|
||||||
|
private Integer Ua_gain;
|
||||||
|
@JSONField(name = "Ub_gain", ordinal = 2)
|
||||||
|
private Integer Ub_gain;
|
||||||
|
@JSONField(name = "Uc_gain", ordinal = 3)
|
||||||
|
private Integer Uc_gain;
|
||||||
|
@JSONField(name = "U0_gain", ordinal = 4)
|
||||||
|
private Integer U0_gain;
|
||||||
|
|
||||||
|
@JSONField(name = "Ia_gain", ordinal = 5)
|
||||||
|
private Integer Ia_gain;
|
||||||
|
@JSONField(name = "Ib_gain", ordinal = 6)
|
||||||
|
private Integer Ib_gain;
|
||||||
|
@JSONField(name = "Ic_gain", ordinal = 7)
|
||||||
|
private Integer Ic_gain;
|
||||||
|
@JSONField(name = "I0_gain", ordinal = 8)
|
||||||
|
private Integer I0_gain;
|
||||||
|
|
||||||
|
@JSONField(name = "Uab_gain", ordinal = 9)
|
||||||
|
private Integer Uab_gain;
|
||||||
|
@JSONField(name = "Ubc_gain", ordinal = 10)
|
||||||
|
private Integer Ubc_gain;
|
||||||
|
@JSONField(name = "Uca_gain", ordinal = 11)
|
||||||
|
private Integer Uca_gain;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.enums;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description:
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2025/2/12 10:15
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
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", "短时间闪变"),
|
||||||
|
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", "角型接线"),
|
||||||
|
|
||||||
|
REAL_PREFIX("real$", "实时数据前缀"),
|
||||||
|
AVG_PREFIX("avg$", "统计数据前缀");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
DetectionCodeEnum(String code, String message) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DetectionCodeEnum getDetectionCodeByCode(String code) {
|
||||||
|
for (DetectionCodeEnum detectionCodeEnum : DetectionCodeEnum.values()) {
|
||||||
|
if (ObjectUtil.equals(code, detectionCodeEnum.getCode())) {
|
||||||
|
return detectionCodeEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UNKNOWN_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024/11/9
|
||||||
|
*/
|
||||||
|
@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", "计划和源关系不存在"),
|
||||||
|
ITEM_TEST_NOT("A020004", "检测项为空"),
|
||||||
|
PLAN_DEV_IP_HAS("A20005","当前计划检测装置ip重复"),
|
||||||
|
SOURCE_NOT_CONNECT("A020006", "源未连接"),
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPT_CHECK_DATA_NOT_EXIST("A020040","测试脚本项暂无配置" ),
|
||||||
|
EXCEED_MAX_TIME("A020041","检测次数超出最大限制!" );
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
DetectionResponseEnum(String code, String message) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.enums;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description:
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/17 15:37
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum SourceOperateCodeEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源状态
|
||||||
|
*/
|
||||||
|
INIT_GATHER("INIT_GATHER", "源初始化"),
|
||||||
|
OPER_GATHER("OPER_GATHER", "源输出"),
|
||||||
|
CLOSE_GATHER("CLOSE_GATHER", "源停止"),
|
||||||
|
HEARTBEAT("HeartBeat", "心跳报文"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终端 INIT_GATHER$01 INIT_GATHER采集初始化,01 统计采集、02 暂态采集、03 实时采集
|
||||||
|
*/
|
||||||
|
DEV_INIT_GATHER_01("INIT_GATHER$01", "统计采集"),
|
||||||
|
DEV_INIT_GATHER_02("INIT_GATHER$02", "实时采集"),
|
||||||
|
DEV_INIT_GATHER_03("INIT_GATHER$03", "暂态采集"),
|
||||||
|
|
||||||
|
DEV_DATA_REQUEST_01("DATA_REQUEST$01", "统计采集申请"),
|
||||||
|
DEV_DATA_REQUEST_02("DATA_REQUEST$02", "实时采集申请"),
|
||||||
|
DEV_DATA_REQUEST_03("DATA_REQUEST$03", "暂态采集申请"),
|
||||||
|
|
||||||
|
QUIT_INIT_01("QUIT_FUNEND$01", "关闭统计申请"),
|
||||||
|
QUIT_INIT_02("QUIT_FUNEND$02", "关闭实时申请"),
|
||||||
|
QUIT_INIT_03("QUIT_FUNEND$03", "关闭暂态申请"),
|
||||||
|
|
||||||
|
|
||||||
|
DATA_CHNFACTOR$01("DATA_CHNFACTOR$01","校验系数获取"),
|
||||||
|
DATA_CHNFACTOR$02("DATA_CHNFACTOR$02","校验系数下发"),
|
||||||
|
|
||||||
|
VERIFY_MAPPING$01("VERIFY_MAPPING$01","脚本与icd校验"),
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求操作类型对应实体中 requestId
|
||||||
|
*/
|
||||||
|
YJC_YTXJY("yjc_ytxjy", "源通讯检测"),
|
||||||
|
YJC_SBTXJY("yjc_sbtxjy", "设备通讯检测"),
|
||||||
|
YJC_XYJY("yjc_xyjy", "协议校验"),
|
||||||
|
YJC_XUJY("YJC_xujy", "相序校验"),
|
||||||
|
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","关闭设备通讯初始化"),
|
||||||
|
QUITE_SOURCE("close_source","关闭源通讯"),
|
||||||
|
ERROR_FLOW_END("error_flow_end","当前流程存在异常结束"),
|
||||||
|
YXT("yxt","心跳"),
|
||||||
|
REPORT_CAT("FTP_SEND$01","处理报告"),
|
||||||
|
|
||||||
|
UNKNOWN_OPERATE("unknown_operate","未知的操作返回,请联系管理员排查"),
|
||||||
|
SOCKET_TIMEOUT("socket_timeout","与源或者装置通讯等待超时"),
|
||||||
|
STOP_TIMEOUT("stop_timeout","暂停时间超过十分钟"),
|
||||||
|
SERVER_ERROR("server_error","服务端主动关闭连接,请稍后再试"),
|
||||||
|
DEVICE_ERROR("device_error","设备主动关闭连接,请稍后再试"),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
// COEFFICIENT_TEST("0","系数校验"),
|
||||||
|
// PRE_TEST("1","预检测"),
|
||||||
|
// FORMAL_TEST("2","正式检测"),
|
||||||
|
// TIME_TEST("3","守时检测"),
|
||||||
|
// PHASE_TEST("4","相序检测"),
|
||||||
|
// TEST_TEM_STOP("5","临时停止"),
|
||||||
|
ALL_TEST("1","全部检测"),
|
||||||
|
RE_ERROR_TEST("2","不合格项复检"),
|
||||||
|
SIMULATE_TEST("4","模拟检测"),
|
||||||
|
//TEST_STOP("7","停止检测"),
|
||||||
|
//FAST_TEST("10","一键检测"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系数校验步骤
|
||||||
|
*/
|
||||||
|
big_start("big_start","大电压系数下装开始"),
|
||||||
|
big_end("big_end","大电压系数下装结束"),
|
||||||
|
small_start("small_start","小电压系数下装开始"),
|
||||||
|
small_end("small_end","小电压系数下装结束"),
|
||||||
|
big_comp_start("big_comp_start","大电压校准开始"),
|
||||||
|
big_comp_end("big_comp_end","大电压校准结束"),
|
||||||
|
small_comp_start("small_comp_start","小电压校准开始"),
|
||||||
|
small_comp_end("small_comp_end","小电压校准结束"),
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ftp文件传送指令
|
||||||
|
*/
|
||||||
|
FTP_SEND_01("FTP_SEND$01", "发送文件"),
|
||||||
|
RDRE$01("RDRE$01", "启动录波");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
private final String msg;
|
||||||
|
|
||||||
|
SourceOperateCodeEnum(String value, String msg) {
|
||||||
|
this.value = value;
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SourceOperateCodeEnum getDictDataEnumByCode(String value) {
|
||||||
|
for (SourceOperateCodeEnum sourceOperateCodeEnum : SourceOperateCodeEnum.values()) {
|
||||||
|
if (ObjectUtil.equals(value, sourceOperateCodeEnum.getValue())) {
|
||||||
|
return sourceOperateCodeEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.enums;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/17 15:37
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum SourceResponseCodeEnum {
|
||||||
|
|
||||||
|
SUCCESS(10200, "请求成功"),
|
||||||
|
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, "测试项解析有误"),
|
||||||
|
SOURCE_CONNECTION_ERROR(10523, "源连接失败"),
|
||||||
|
SOURCE_CONTROL_ERROR(10524, "获取源控制权失败"),
|
||||||
|
RESET_ERROR(10525, "重置源失败"),
|
||||||
|
STOP_ERROR(10526, "停止源失败"),
|
||||||
|
NOT_INITIALIZED(10527, "源未进行初始化"),
|
||||||
|
TARGET_SOURCE_ERROR(10528, "目标源有误(该用户已控制其他源,在关闭前无法操作新的源)"),
|
||||||
|
UNABLE_TO_RESPOND(10529, "源状态有误,无法响应报文(例如源处于输出状态,无法响应初始化报文)"),
|
||||||
|
UNKNOWN_ERROR(-1, "未知异常"),
|
||||||
|
|
||||||
|
|
||||||
|
//通讯模块
|
||||||
|
DEV_ERROR(10550,"设备连接异常"),
|
||||||
|
DEV_TARGET(10551,"设备触发报告异常"),
|
||||||
|
RE_OPERATE(10552,"重复的初始化操作"),
|
||||||
|
COMMUNICATION_ERR(10553,"通讯模块通讯异常"),
|
||||||
|
DATA_RESOLVE(10554,"报文解析异常"),
|
||||||
|
NO_INIT_DEV(10556,"不存在上线的设备"),
|
||||||
|
|
||||||
|
//自定义前端展示消息
|
||||||
|
SOCKET_ERROR(25000,"服务端连接失败"),
|
||||||
|
ALL_SUCCESS(25001,"校验成功"),
|
||||||
|
FAIL(25002,"失败"),
|
||||||
|
ALL_FAIL(25003,"校验失败"),
|
||||||
|
RECEIVE_DATA_TIME_OUT(25004,"接收数据超时"),
|
||||||
|
REAL_DATA_CHECK_FAIL(25005,"实时数据校验失败")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
private final Integer code;
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
SourceResponseCodeEnum(Integer code, String message) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMsgByValue(Integer code) {
|
||||||
|
for (SourceResponseCodeEnum state : SourceResponseCodeEnum.values()) {
|
||||||
|
if (state.getCode().equals(code)) {
|
||||||
|
return state.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SourceResponseCodeEnum getDictDataEnumByCode(Integer code) {
|
||||||
|
for (SourceResponseCodeEnum sourceResponseCodeEnum : SourceResponseCodeEnum.values()) {
|
||||||
|
if (ObjectUtil.equals(code, sourceResponseCodeEnum.getCode())) {
|
||||||
|
return sourceResponseCodeEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.param;
|
||||||
|
|
||||||
|
import com.njcn.gather.pojo.constant.DetectionValidMessage;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2025-07-18
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ContrastDetectionParam {
|
||||||
|
|
||||||
|
@ApiModelProperty("检测计划ID")
|
||||||
|
@NotBlank(message = DetectionValidMessage.PLAN_ID_NOT_BLANK)
|
||||||
|
private String planId;
|
||||||
|
|
||||||
|
@ApiModelProperty("用户ID,唯一标识")
|
||||||
|
private String loginName;
|
||||||
|
|
||||||
|
@ApiModelProperty("被检设备ID列表")
|
||||||
|
@NotEmpty(message = DetectionValidMessage.DEV_IDS_NOT_EMPTY)
|
||||||
|
private List<String> devIds;
|
||||||
|
|
||||||
|
@ApiModelProperty("标准设备ID列表")
|
||||||
|
@NotEmpty(message = DetectionValidMessage.STANDARD_DEV_IDS_NOT_EMPTY)
|
||||||
|
private List<String> standardDevIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key为被检设备ID_检测点序号、value为 标准设备ID_检测点序号
|
||||||
|
*/
|
||||||
|
@ApiModelProperty("配对关系")
|
||||||
|
@NotEmpty(message = DetectionValidMessage.PAIRS_NOT_EMPTY)
|
||||||
|
private Map<String, String> pairs;
|
||||||
|
/**
|
||||||
|
* 检测项列表。第一个元素为预检测、第二个元素为系数校准、第三个元素为正式检测
|
||||||
|
*/
|
||||||
|
private List<Boolean> testItemList;
|
||||||
|
|
||||||
|
private String userId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.param;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wr
|
||||||
|
* @description
|
||||||
|
* @date 2024/12/18 9:17
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
public class DevPhaseSequenceParam {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置下测点集合
|
||||||
|
*/
|
||||||
|
@JSONField(name = "moniterIdList", ordinal = 1)
|
||||||
|
private List<String> moniterIdList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置需要的取值(平均值/电压有效值)
|
||||||
|
*/
|
||||||
|
@JSONField(name = "dataType", ordinal = 2)
|
||||||
|
private List<String> dataType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取多少组数据
|
||||||
|
*/
|
||||||
|
@JSONField(name = "readCount", ordinal = 3)
|
||||||
|
private Integer readCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略多少组数据
|
||||||
|
*/
|
||||||
|
@JSONField(name = "ignoreCount", ordinal = 4)
|
||||||
|
private Integer ignoreCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.param;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wr
|
||||||
|
* @description
|
||||||
|
* @date 2024/12/11 13:45
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PreDetectionParam {
|
||||||
|
|
||||||
|
// "1"-"全部检测" , "2"-"不合格项复检"
|
||||||
|
private String reCheckType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测计划id
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "计划id不可为空")
|
||||||
|
private String planId;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户功能组成唯一标识 zhangsan_test
|
||||||
|
*/
|
||||||
|
private String userPageId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测终端id集合
|
||||||
|
*/
|
||||||
|
@NotEmpty(message = "装置不能为空")
|
||||||
|
private List<String> devIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测脚本Id
|
||||||
|
*/
|
||||||
|
private String scriptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源id
|
||||||
|
*/
|
||||||
|
private String sourceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属误差体系
|
||||||
|
*/
|
||||||
|
private String errorSysId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动生成,用于生成数据表后缀
|
||||||
|
*/
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
private Boolean sendWebMsg;
|
||||||
|
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 温度
|
||||||
|
*/
|
||||||
|
private Float temperature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相对湿度
|
||||||
|
*/
|
||||||
|
private Float humidity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测项列表。第一个元素为预检测、第二个元素为系数校准、第三个元素为正式检测
|
||||||
|
*/
|
||||||
|
private List<Boolean> testItemList;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.param;
|
||||||
|
|
||||||
|
import com.njcn.gather.pojo.constant.DetectionValidMessage;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2025-03-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SimulateDetectionParam {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作类型 9.模拟检测
|
||||||
|
*/
|
||||||
|
private String operateType = "9";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户功能组成唯一标识 zhangsan_test
|
||||||
|
*/
|
||||||
|
private String userPageId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测脚本Id
|
||||||
|
*/
|
||||||
|
private String scriptId;
|
||||||
|
|
||||||
|
@NotNull(message = DetectionValidMessage.INDEX_NOT_NULL)
|
||||||
|
private Integer scriptIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源id
|
||||||
|
*/
|
||||||
|
@NotBlank(message = DetectionValidMessage.SOURCE_ID_NOT_BLANK)
|
||||||
|
private String sourceId;
|
||||||
|
}
|
||||||
@@ -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,85 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.po;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wr
|
||||||
|
* @description
|
||||||
|
* @date 2024/12/18 9:09
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
public class DevData {
|
||||||
|
|
||||||
|
@JSONField(name = "Time", ordinal = 1)
|
||||||
|
private String time;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备ip_通道号 这种形式
|
||||||
|
*/
|
||||||
|
@JSONField(name = "ID", ordinal = 2)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@JSONField(name = "result", ordinal = 3)
|
||||||
|
private Boolean result;
|
||||||
|
|
||||||
|
@JSONField(name = "SqlData", ordinal = 4)
|
||||||
|
private List<SqlDataDTO> sqlData;
|
||||||
|
|
||||||
|
@JSONField(name = "SqlDataHarm", ordinal = 5)
|
||||||
|
private List<SqlDataHarmDTO> sqlDataHarm;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
public static class SqlDataDTO {
|
||||||
|
//类型 平均值 最大值 最小值 CP95值 实时值
|
||||||
|
@JSONField(name = "type", ordinal = 1)
|
||||||
|
private String type;
|
||||||
|
//指标 电流有效值
|
||||||
|
@JSONField(name = "desc", ordinal = 2)
|
||||||
|
private String desc;
|
||||||
|
@JSONField(name = "list", ordinal = 3)
|
||||||
|
private ListDTO list;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
public static class ListDTO {
|
||||||
|
@JSONField(name = "A", ordinal = 1)
|
||||||
|
private Double a;
|
||||||
|
@JSONField(name = "B", ordinal = 2)
|
||||||
|
private Double b;
|
||||||
|
@JSONField(name = "C", ordinal = 3)
|
||||||
|
private Double c;
|
||||||
|
@JSONField(name = "T", ordinal = 4)
|
||||||
|
private Double t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
public static class SqlDataHarmDTO {
|
||||||
|
@JSONField(name = "type", ordinal = 1)
|
||||||
|
private String type;
|
||||||
|
@JSONField(name = "desc", ordinal = 2)
|
||||||
|
private String desc;
|
||||||
|
@JSONField(name = "num", ordinal = 3)
|
||||||
|
private Integer num;
|
||||||
|
@JSONField(name = "list", ordinal = 4)
|
||||||
|
private ListDTO list;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
public static class ListDTO {
|
||||||
|
@JSONField(name = "A", ordinal = 1)
|
||||||
|
private List<String> a;
|
||||||
|
@JSONField(name = "B", ordinal = 2)
|
||||||
|
private List<String> b;
|
||||||
|
@JSONField(name = "C", ordinal = 3)
|
||||||
|
private List<String> c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.po;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2025-04-10
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
public class IcdCheckData {
|
||||||
|
|
||||||
|
private String icdType;
|
||||||
|
|
||||||
|
private List<ResultData> resultData;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
public static class ResultData {
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
private String desc;
|
||||||
|
|
||||||
|
private DevData.SqlDataDTO.ListDTO phaseResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.po;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wr
|
||||||
|
* @description
|
||||||
|
* @date 2024/12/18 9:09
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SourceCompareDev {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置名称
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 1)
|
||||||
|
private String devName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置ip
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 2)
|
||||||
|
private String ip;
|
||||||
|
/**
|
||||||
|
* 装置通道
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 3)
|
||||||
|
private String lineNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 3)
|
||||||
|
private String desc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否合格
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 4)
|
||||||
|
private Boolean isQualified;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源数据
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 5)
|
||||||
|
private Map<String, Double> sourceData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置数据
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 6)
|
||||||
|
private Map<String, Double> DevData;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Info {
|
||||||
|
/**
|
||||||
|
* 装置名称
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 1)
|
||||||
|
private String devName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置ip
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 2)
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 3)
|
||||||
|
private String desc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置下所有通道信息
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 4)
|
||||||
|
List<SourceCompareDev> devNumlist;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.vo;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: cdf
|
||||||
|
* @CreateTime: 2025-01-04
|
||||||
|
* @Description: 系数校准前端返回实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CoefficientVO {
|
||||||
|
|
||||||
|
@JSONField(name = "type", ordinal = 0)
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
@JSONField(name = "devName", ordinal = 1)
|
||||||
|
private String devName;
|
||||||
|
|
||||||
|
@JSONField(name = "monitorNum", ordinal = 2)
|
||||||
|
private String monitorNum;
|
||||||
|
|
||||||
|
@JSONField(name = "desc", ordinal = 3)
|
||||||
|
private String desc;
|
||||||
|
|
||||||
|
|
||||||
|
@JSONField(name = "aVuData", ordinal = 4)
|
||||||
|
private String aVuData;
|
||||||
|
|
||||||
|
@JSONField(name = "aVuXi", ordinal = 5)
|
||||||
|
private String aVuXi;
|
||||||
|
|
||||||
|
@JSONField(name = "bVuData", ordinal = 6)
|
||||||
|
private String bVuData;
|
||||||
|
|
||||||
|
@JSONField(name = "bVuXi", ordinal = 7)
|
||||||
|
private String bVuXi;
|
||||||
|
|
||||||
|
@JSONField(name = "cVuData", ordinal = 8)
|
||||||
|
private String cVuData;
|
||||||
|
|
||||||
|
@JSONField(name = "cVuXi", ordinal = 9)
|
||||||
|
private String cVuXi;
|
||||||
|
|
||||||
|
@JSONField(name = "aIeData", ordinal = 10)
|
||||||
|
private String aIeData;
|
||||||
|
|
||||||
|
@JSONField(name = "aIeXi", ordinal = 11)
|
||||||
|
private String aIeXi;
|
||||||
|
|
||||||
|
@JSONField(name = "bIeData", ordinal = 12)
|
||||||
|
private String bIeData;
|
||||||
|
|
||||||
|
@JSONField(name = "bIeXi", ordinal = 13)
|
||||||
|
private String bIeXi;
|
||||||
|
|
||||||
|
@JSONField(name = "cIeData", ordinal = 14)
|
||||||
|
private String cIeData;
|
||||||
|
|
||||||
|
@JSONField(name = "cIeXi", ordinal = 15)
|
||||||
|
private String cIeXi;
|
||||||
|
|
||||||
|
|
||||||
|
@JSONField(name = "aV", ordinal = 16)
|
||||||
|
private String aV;
|
||||||
|
|
||||||
|
@JSONField(name = "bV", ordinal = 17)
|
||||||
|
private String bV;
|
||||||
|
|
||||||
|
@JSONField(name = "cV", ordinal = 18)
|
||||||
|
private String cV;
|
||||||
|
|
||||||
|
@JSONField(name = "aI", ordinal = 19)
|
||||||
|
private String aI;
|
||||||
|
|
||||||
|
@JSONField(name = "bI", ordinal = 20)
|
||||||
|
private String bI;
|
||||||
|
|
||||||
|
@JSONField(name = "cI", ordinal = 21)
|
||||||
|
private String cI;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标识校验不合格的为 0 合格项为 1
|
||||||
|
*/
|
||||||
|
private Integer resultFlag;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DevParameter{
|
||||||
|
|
||||||
|
private Double devVolt;
|
||||||
|
|
||||||
|
private Double devCurr;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.vo;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wr
|
||||||
|
* @description
|
||||||
|
* @date 2024/12/20 13:52
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DetectionData {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第几次谐波
|
||||||
|
*/
|
||||||
|
private Double num;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1.合格 2.不合格 3.网络超时 4.无法处理 5.不参与误差比较
|
||||||
|
*/
|
||||||
|
private Integer isData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置原始数据
|
||||||
|
*/
|
||||||
|
private Double data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测源定值
|
||||||
|
*/
|
||||||
|
private Double resultData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 误差范围
|
||||||
|
*/
|
||||||
|
private String radius;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 误差值
|
||||||
|
*/
|
||||||
|
private BigDecimal errorData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单位
|
||||||
|
*/
|
||||||
|
private String unit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 误差体系详情ID(比对式使用)
|
||||||
|
*/
|
||||||
|
private String errorDtlId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CN_Gather
|
||||||
|
*
|
||||||
|
* @author cdf
|
||||||
|
* @date 2024/12/29
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DetectionResultInfoVO {
|
||||||
|
|
||||||
|
private String freq;
|
||||||
|
|
||||||
|
private String Ua;
|
||||||
|
|
||||||
|
private String Ub;
|
||||||
|
|
||||||
|
private String Uc;
|
||||||
|
|
||||||
|
private String Ia;
|
||||||
|
|
||||||
|
private String Ib;
|
||||||
|
|
||||||
|
private String Ic;
|
||||||
|
|
||||||
|
private List<Result> resultList;
|
||||||
|
|
||||||
|
private List<OriginalInfo> resultInfoList;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测结果
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Result{
|
||||||
|
|
||||||
|
private Integer channelNum;
|
||||||
|
|
||||||
|
private Double standardVal;
|
||||||
|
|
||||||
|
private Double aVal;
|
||||||
|
|
||||||
|
private Double aLimit;
|
||||||
|
|
||||||
|
private Double bVal;
|
||||||
|
|
||||||
|
private Double bLimit;
|
||||||
|
|
||||||
|
private Double cVal;
|
||||||
|
|
||||||
|
private Double cLimit;
|
||||||
|
|
||||||
|
private Double maxError;
|
||||||
|
|
||||||
|
private Integer result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始数据
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class OriginalInfo{
|
||||||
|
|
||||||
|
private LocalDateTime time;
|
||||||
|
|
||||||
|
private Double aVal;
|
||||||
|
|
||||||
|
private Double bVal;
|
||||||
|
|
||||||
|
private Double cVal;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: cdf
|
||||||
|
* @CreateTime: 2024-12-26
|
||||||
|
* @Description: 装置测点检测结果
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DevLineTestResult {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测项code
|
||||||
|
*/
|
||||||
|
private String scriptName;
|
||||||
|
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
private String deviceName;
|
||||||
|
|
||||||
|
private Integer[] chnResult;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.vo;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wr
|
||||||
|
* @description
|
||||||
|
* @date 2024/12/13 9:09
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SocketDataMsg {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标识不同业务
|
||||||
|
*/
|
||||||
|
private String type = "aaa";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求id,确保接收到响应时,知晓是针对的哪次请求的应答
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 1)
|
||||||
|
private String requestId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源初始化 INIT_GATHER$01 INIT_GATHER采集初始化,01 统计采集、02 暂态采集、03 实时采集
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 2)
|
||||||
|
private String operateCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据体,传输前需要将对象、Array等转为String
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 4)
|
||||||
|
private String data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* code码
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 3)
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.vo;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wr
|
||||||
|
* @description socket 通用发送报文请求
|
||||||
|
* @date 2024/12/11 15:57
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SocketMsg<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求id,确保接收到响应时,知晓是针对的哪次请求的应答
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 1)
|
||||||
|
private String requestId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源初始化 INIT_GATHER$01 INIT_GATHER采集初始化,01 统计采集、02 暂态采集、03 实时采集
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 2)
|
||||||
|
private String operateCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据体,传输前需要将对象、Array等转为String
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 3)
|
||||||
|
private T data;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.vo;
|
||||||
|
|
||||||
|
import com.njcn.gather.detection.pojo.enums.SourceResponseCodeEnum;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chendaofei
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class WebSocketVO<T> {
|
||||||
|
|
||||||
|
private String type = "aaa";
|
||||||
|
|
||||||
|
private String requestId;
|
||||||
|
|
||||||
|
private String operateCode;
|
||||||
|
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
private String desc;
|
||||||
|
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.njcn.gather.detection.service;
|
||||||
|
|
||||||
|
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
|
||||||
|
* @description 预检测流程
|
||||||
|
* @date 2024/12/10 13:44
|
||||||
|
*/
|
||||||
|
public interface PreDetectionService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源通讯校验socket入参拼接
|
||||||
|
*
|
||||||
|
* @param param
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/11 13:26
|
||||||
|
*/
|
||||||
|
void sourceCommunicationCheck(PreDetectionParam param);
|
||||||
|
|
||||||
|
|
||||||
|
void coefficientCheck(PreDetectionParam param);
|
||||||
|
|
||||||
|
|
||||||
|
boolean temStopTest();
|
||||||
|
|
||||||
|
|
||||||
|
boolean restartTemTest(PreDetectionParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟测试-源通讯校验
|
||||||
|
*
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
void ytxCheckSimulate(SimulateDetectionParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟测试-向源发送脚本
|
||||||
|
*
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
void sendScript(SimulateDetectionParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟测试-停止
|
||||||
|
*
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
void closeTestSimulate(SimulateDetectionParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @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
@@ -0,0 +1,410 @@
|
|||||||
|
package com.njcn.gather.detection.service.impl;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
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.constant.DetectionCommunicateConstant;
|
||||||
|
import com.njcn.gather.detection.pojo.enums.DetectionResponseEnum;
|
||||||
|
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||||
|
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 com.njcn.gather.detection.pojo.vo.SocketMsg;
|
||||||
|
import com.njcn.gather.detection.service.PreDetectionService;
|
||||||
|
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.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.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;
|
||||||
|
import com.njcn.gather.script.service.IPqScriptCheckDataService;
|
||||||
|
import com.njcn.gather.script.service.IPqScriptDtlsService;
|
||||||
|
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.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class PreDetectionServiceImpl implements PreDetectionService {
|
||||||
|
|
||||||
|
private final IPqDevService iPqDevService;
|
||||||
|
private final IDictDataService dictDataService;
|
||||||
|
private final IAdPlanService iAdPlanService;
|
||||||
|
private final IAdPlanSourceService adPlanSourceService;
|
||||||
|
private final IPqSourceService pqSourceService;
|
||||||
|
private final IPqScriptDtlsService pqScriptDtlsService;
|
||||||
|
|
||||||
|
private final SocketDevResponseService socketDevResponseService;
|
||||||
|
private final SocketSourceResponseService socketSourceResponseService;
|
||||||
|
private final SocketContrastResponseService socketContrastResponseService;
|
||||||
|
private final IPqScriptCheckDataService iPqScriptCheckDataService;
|
||||||
|
private final SocketManager socketManager;
|
||||||
|
|
||||||
|
@Value("${log.homeDir}")
|
||||||
|
private String alignDataFilePath;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sourceCommunicationCheck(PreDetectionParam param) {
|
||||||
|
// 参数校验,目前仅检查IP是否重复,后续可在里面扩展
|
||||||
|
checkDevIp(param.getDevIds());
|
||||||
|
//用于处理异常导致的socket通道未关闭,socket交互异常
|
||||||
|
DetectionCommunicateUtil.checkCommunicateChannel(param);
|
||||||
|
/*
|
||||||
|
* 先组装源通讯协议
|
||||||
|
* 查询计划什么模式的(除了对比式,其他都是一个计划对应一个源)
|
||||||
|
*/
|
||||||
|
AdPlan plan = iAdPlanService.getById(param.getPlanId());
|
||||||
|
param.setScriptId(plan.getScriptId());
|
||||||
|
param.setErrorSysId(plan.getErrorSysId());
|
||||||
|
param.setCode(String.valueOf(plan.getCode()));
|
||||||
|
if (ObjectUtil.isNotNull(plan)) {
|
||||||
|
String code = dictDataService.getDictDataById(plan.getPattern()).getCode();
|
||||||
|
DictDataEnum dictDataEnumByCode = DictDataEnum.getDictDataEnumByCode(code);
|
||||||
|
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....
|
||||||
|
DetectionCommunicateUtil.checkCommunicateChannel(param);
|
||||||
|
AdPlanSource planSource = adPlanSourceService.getOne(new LambdaQueryWrapper<AdPlanSource>()
|
||||||
|
.eq(AdPlanSource::getPlanId, param.getPlanId())
|
||||||
|
);
|
||||||
|
if (ObjectUtil.isNotNull(planSource)) {
|
||||||
|
SourceInitialize sourceParam = pqSourceService.getSourceInitializeParam(planSource.getSourceId());
|
||||||
|
if (ObjectUtil.isNotNull(sourceParam)) {
|
||||||
|
//开始组装socket报文请求头
|
||||||
|
socketDevResponseService.initList(param);
|
||||||
|
socketSourceResponseService.initList(param);
|
||||||
|
SocketMsg<String> msg = new SocketMsg<>();
|
||||||
|
msg.setRequestId(SourceOperateCodeEnum.YJC_YTXJY.getValue());
|
||||||
|
msg.setOperateCode(SourceOperateCodeEnum.INIT_GATHER.getValue());
|
||||||
|
msg.setData(JSON.toJSONString(sourceParam));
|
||||||
|
param.setSourceId(sourceParam.getSourceId());
|
||||||
|
// 使用智能发送工具类,自动管理连接
|
||||||
|
socketManager.smartSendToSource(param, JSON.toJSONString(msg));
|
||||||
|
} else {
|
||||||
|
throw new BusinessException(DetectionResponseEnum.SOURCE_INFO_NOT);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new BusinessException(DetectionResponseEnum.PLAN_AND_SOURCE_NOT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送源通信校验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) {
|
||||||
|
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());
|
||||||
|
if (ObjectUtil.isNotNull(sourceParam)) {
|
||||||
|
//开始组装socket报文请求头
|
||||||
|
socketDevResponseService.initList(param);
|
||||||
|
socketSourceResponseService.initList(param);
|
||||||
|
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||||
|
socketMsg.setRequestId(SourceOperateCodeEnum.YJC_YTXJY.getValue());
|
||||||
|
socketMsg.setOperateCode(SourceOperateCodeEnum.INIT_GATHER.getValue());
|
||||||
|
socketMsg.setData(JSON.toJSONString(sourceParam));
|
||||||
|
//使用智能发送工具类,自动管理与源控程序的socket连接
|
||||||
|
socketManager.smartSendToSource(param, JSON.toJSONString(socketMsg));
|
||||||
|
} else {
|
||||||
|
throw new BusinessException(DetectionResponseEnum.SOURCE_INFO_NOT);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new BusinessException(DetectionResponseEnum.PLAN_AND_SOURCE_NOT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送源通信校验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());
|
||||||
|
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));
|
||||||
|
// 使用智能发送工具类,自动管理连接
|
||||||
|
socketManager.smartSendToSource(param, JSON.toJSONString(socketMsg));
|
||||||
|
} else {
|
||||||
|
throw new BusinessException(DetectionResponseEnum.SOURCE_INFO_NOT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean temStopTest() {
|
||||||
|
FormalTestManager.stopFlag = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean restartTemTest(PreDetectionParam param) {
|
||||||
|
FormalTestManager.stopFlag = false;
|
||||||
|
socketDevResponseService.initRestart();
|
||||||
|
List<SourceIssue> sourceIssueList = SocketManager.getSourceList();
|
||||||
|
if (CollUtil.isNotEmpty(sourceIssueList)) {
|
||||||
|
SourceIssue sourceIssues = SocketManager.getSourceList().get(0);
|
||||||
|
SocketMsg<String> xuMsg = new SocketMsg<>();
|
||||||
|
xuMsg.setOperateCode(SourceOperateCodeEnum.OPER_GATHER.getValue());
|
||||||
|
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 是否最终检测完成需要推送给用户 检测完成
|
||||||
|
PqScriptCheckDataParam checkDataParam = new PqScriptCheckDataParam();
|
||||||
|
checkDataParam.setScriptId(param.getScriptId());
|
||||||
|
checkDataParam.setIsValueTypeName(false);
|
||||||
|
List<String> adType = iPqScriptCheckDataService.getValueType(checkDataParam);
|
||||||
|
|
||||||
|
iPqDevService.updateResult(param.getDevIds(), adType, param.getCode(), param.getUserId(), param.getTemperature(), param.getHumidity());
|
||||||
|
CnSocketUtil.quitSend(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void ytxCheckSimulate(SimulateDetectionParam param) {
|
||||||
|
PreDetectionParam preDetectionParam = new PreDetectionParam();
|
||||||
|
preDetectionParam.setSourceId(param.getSourceId());
|
||||||
|
preDetectionParam.setUserPageId(param.getUserPageId());
|
||||||
|
preDetectionParam.setSendWebMsg(true);
|
||||||
|
|
||||||
|
DetectionCommunicateUtil.checkCommunicateChannel(preDetectionParam);
|
||||||
|
sendYtxSocketSimulate(preDetectionParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendScript(SimulateDetectionParam param) {
|
||||||
|
Channel channel = SocketManager.getChannelByUserId(param.getUserPageId() + DetectionCommunicateConstant.SOURCE);
|
||||||
|
if (Objects.isNull(channel) || !channel.isActive()) {
|
||||||
|
// 进行源通信连接
|
||||||
|
PreDetectionParam preDetectionParam = new PreDetectionParam();
|
||||||
|
preDetectionParam.setSourceId(param.getSourceId());
|
||||||
|
preDetectionParam.setUserPageId(param.getUserPageId());
|
||||||
|
preDetectionParam.setSendWebMsg(false);
|
||||||
|
this.sendYtxSocketSimulate(preDetectionParam);
|
||||||
|
}
|
||||||
|
//组装源控制脚本
|
||||||
|
PqScriptIssueParam issueParam = new PqScriptIssueParam();
|
||||||
|
issueParam.setSourceId(param.getSourceId());
|
||||||
|
issueParam.setScriptId(param.getScriptId());
|
||||||
|
issueParam.setType(1);
|
||||||
|
issueParam.setIsPhaseSequence(SourceOperateCodeEnum.SIMULATE_TEST.getValue());
|
||||||
|
|
||||||
|
List<SourceIssue> sourceIssues = pqScriptDtlsService.listSourceIssue(issueParam);
|
||||||
|
sourceIssues = sourceIssues.stream()
|
||||||
|
.filter(s -> s.getIndex().equals(param.getScriptIndex()))
|
||||||
|
.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() + CnSocketUtil.STEP_TAG + type);
|
||||||
|
socketMsg.setData(JSON.toJSONString(sourceIssues.get(0)));
|
||||||
|
SocketManager.sendMsg(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, JSON.toJSONString(socketMsg));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeTestSimulate(SimulateDetectionParam param) {
|
||||||
|
Channel channel = SocketManager.getChannelByUserId(param.getUserPageId() + DetectionCommunicateConstant.SOURCE);
|
||||||
|
if (Objects.isNull(channel) || !channel.isActive()) {
|
||||||
|
throw new BusinessException(DetectionResponseEnum.SOURCE_NOT_CONNECT);
|
||||||
|
}
|
||||||
|
SourceInitialize sourceParam = pqSourceService.getSourceInitializeParam(param.getSourceId());
|
||||||
|
PreDetectionParam preDetectionParam = new PreDetectionParam();
|
||||||
|
preDetectionParam.setSourceId(sourceParam.getSourceId());
|
||||||
|
preDetectionParam.setUserPageId(param.getUserPageId());
|
||||||
|
CnSocketUtil.quitSendSource(preDetectionParam);
|
||||||
|
WebServiceManager.removePreDetectionParam();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startContrastTest(ContrastDetectionParam param) {
|
||||||
|
// 参数校验,目前仅检查IP是否重复,后续可在里面扩展
|
||||||
|
checkDevIp(param.getDevIds());
|
||||||
|
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) {
|
||||||
|
Map<String, List<PreDetection>> map = new HashMap<>(1);
|
||||||
|
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 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 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_02.getValue());
|
||||||
|
socketMsg.setData(jsonString);
|
||||||
|
PreDetectionParam preDetectionParam = new PreDetectionParam();
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验被检设备中是否存在IP重复的
|
||||||
|
*/
|
||||||
|
private void checkDevIp(List<String> devIds) {
|
||||||
|
List<PqDev> pqDevList = iPqDevService.listByIds(devIds);
|
||||||
|
List<String> ipList = pqDevList.stream().map(PqDev::getIp).distinct().collect(Collectors.toList());
|
||||||
|
if (ipList.size() != devIds.size()) {
|
||||||
|
throw new BusinessException(DetectionResponseEnum.PLAN_DEV_IP_HAS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,426 @@
|
|||||||
|
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 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.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
|
||||||
|
* @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 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 矫正后的相角值,范围在[-180°, 180°]内
|
||||||
|
*/
|
||||||
|
public static Double adjustPhase(Double phase) {
|
||||||
|
if (phase == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 使用模运算将角度标准化到[-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 normalizedPhase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断被检设备数据与标准设备数据的时间是否对齐
|
||||||
|
* <p>
|
||||||
|
* 数据对齐的判断标准(满足任一条件即可):
|
||||||
|
* 1. 将时间戳四舍五入到秒级精度后完全相等(处理跨秒边界情况)
|
||||||
|
* 2. 两个时间戳的毫秒差值小于容差值(处理同秒内的精确对齐)
|
||||||
|
* <p>
|
||||||
|
* 示例:499ms vs 509ms → 四舍五入后不同秒,但差值仅10ms < 100ms → 对齐
|
||||||
|
*
|
||||||
|
* @param devData 被检设备数据,包含时间戳信息
|
||||||
|
* @param standardDevData 标准设备数据,包含时间戳信息
|
||||||
|
* @return true表示数据时间对齐,false表示不对齐
|
||||||
|
*/
|
||||||
|
public static boolean isAlignData(DevData devData, DevData standardDevData) {
|
||||||
|
if (ObjectUtil.isNotNull(devData) && ObjectUtil.isNotNull(standardDevData)) {
|
||||||
|
// 获取两个设备数据的时间戳(毫秒)
|
||||||
|
long devMillis = getMillis(devData.getTime());
|
||||||
|
long standardMillis = getMillis(standardDevData.getTime());
|
||||||
|
|
||||||
|
// 方式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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将字符串日期时间转换为LocalDateTime对象
|
||||||
|
* <p>
|
||||||
|
* 使用指定的格式解析时间字符串,支持带时区的ISO 8601格式。
|
||||||
|
* 解析时会将时间统一转换为UTC时区的LocalDateTime。
|
||||||
|
*
|
||||||
|
* @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对象
|
||||||
|
return zonedDateTime.toLocalDateTime();
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
log.error("日期时间字符串格式错误: {}", e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字符串日期时间对应的UTC毫秒时间戳
|
||||||
|
* <p>
|
||||||
|
* 使用默认的ISO_DATE_TIME格式解析时间字符串,
|
||||||
|
* 并转换为UTC时区的毫秒时间戳。
|
||||||
|
*
|
||||||
|
* @param dateTimeStr ISO 8601格式的日期时间字符串
|
||||||
|
* @return UTC时区的毫秒时间戳
|
||||||
|
*/
|
||||||
|
public static long getMillis(String dateTimeStr) {
|
||||||
|
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对应的UTC毫秒时间戳
|
||||||
|
* <p>
|
||||||
|
* 将LocalDateTime对象转换为UTC时区的毫秒时间戳。
|
||||||
|
*
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断数值是否为零(在容差范围内)
|
||||||
|
* <p>
|
||||||
|
* 使用BigDecimal进行精确计算,避免浮点数精度问题。
|
||||||
|
* 当数值的绝对值小于预设阈值(0.01)时,认为该数值为零。
|
||||||
|
* 主要用于电流等物理量的零值判断。
|
||||||
|
*
|
||||||
|
* @param value 待判断的数值,null值被认为是零
|
||||||
|
* @param ratedCurrent 额定电流,用于计算阈值
|
||||||
|
* @return true表示数值为零(在容差范围内),false表示非零
|
||||||
|
*/
|
||||||
|
public static boolean isZero(Double value, Double ratedCurrent) {
|
||||||
|
if (value == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
double threshold = 0.01 * ratedCurrent;
|
||||||
|
BigDecimal bd = BigDecimal.valueOf(value);
|
||||||
|
return bd.subtract(BigDecimal.ZERO).abs().compareTo(BigDecimal.valueOf(threshold)) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取CP95分位数值
|
||||||
|
* <p>
|
||||||
|
* CP95表示95%分位数,即有95%的数据小于等于此值。
|
||||||
|
* 算法逻辑:
|
||||||
|
* <ul>
|
||||||
|
* <li>数据量=1时:返回该数据</li>
|
||||||
|
* <li>数据量<21时:返回第2个数据(索引1)</li>
|
||||||
|
* <li>数据量≥21时:计算5%位置的数据(适用于从大到小排序的数据)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param t 已排序的数据列表(从大到小排序)
|
||||||
|
* @return CP95分位数值列表,包含单个元素
|
||||||
|
*/
|
||||||
|
public static List<Double> getCP95Doubles(List<Double> t) {
|
||||||
|
if (CollUtil.isEmpty(t)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单个数据直接返回
|
||||||
|
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分位数值在列表中的索引位置
|
||||||
|
* <p>
|
||||||
|
* 计算CP95分位数在已排序列表中的索引位置。
|
||||||
|
* 索引计算规则与getCP95Doubles方法保持一致。
|
||||||
|
*
|
||||||
|
* @param t 已排序的数据列表(从大到小排序)
|
||||||
|
* @return CP95分位数的索引位置,列表为空时返回-1
|
||||||
|
*/
|
||||||
|
public static int getCP95Idx(List<Double> t) {
|
||||||
|
if (CollUtil.isEmpty(t)) {
|
||||||
|
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 去除最大最小值后的数据列表副本
|
||||||
|
*/
|
||||||
|
public static List<Double> getSectionValueDoubles(List<Double> t) {
|
||||||
|
if (CollUtil.isEmpty(t) || t.size() <= MIN_DATA_SIZE_FOR_SECTION_VALUE) {
|
||||||
|
return new ArrayList<>(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 包含平均值的单元素列表,输入为空时返回空列表
|
||||||
|
*/
|
||||||
|
public static List<Double> getAvgDoubles(List<Double> t) {
|
||||||
|
if (CollUtil.isEmpty(t)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算列表中所有数值的算术平均值
|
||||||
|
double average = t.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
|
||||||
|
// 将平均值包装为单元素列表返回
|
||||||
|
return Collections.singletonList(average);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对数据列表进行排序并返回原始索引序列
|
||||||
|
* <p>
|
||||||
|
* 使用选择排序算法对列表进行排序,同时跟踪每个元素的原始索引位置。
|
||||||
|
* 这样可以在数据排序后仍然知道每个数据在原始列表中的位置。
|
||||||
|
*
|
||||||
|
* <b>注意:</b>该方法会直接修改输入的列表。
|
||||||
|
*
|
||||||
|
* @param list 待排序的数据列表(会被直接修改)
|
||||||
|
* @param isAsc 排序方式,true为升序,false为降序
|
||||||
|
* @return 排序后各元素在原始列表中的索引位置
|
||||||
|
*/
|
||||||
|
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 targetIdx = i;
|
||||||
|
// 在未排序部分寻找最值
|
||||||
|
for (int j = i + 1; j < list.size(); j++) {
|
||||||
|
if (isAsc) {
|
||||||
|
// 升序:寻找最小值
|
||||||
|
if (list.get(j) < list.get(targetIdx)) {
|
||||||
|
targetIdx = j;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 降序:寻找最大值
|
||||||
|
if (list.get(j) > list.get(targetIdx)) {
|
||||||
|
targetIdx = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 交换数据值和对应的索引
|
||||||
|
if (targetIdx != i) {
|
||||||
|
// 交换数据值
|
||||||
|
double temp = list.get(i);
|
||||||
|
list.set(i, list.get(targetIdx));
|
||||||
|
list.set(targetIdx, temp);
|
||||||
|
|
||||||
|
// 交换对应的原始索引
|
||||||
|
int tempIdx = indexList.get(i);
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.njcn.gather.detection.util.business;
|
||||||
|
|
||||||
|
import com.njcn.gather.detection.pojo.constant.DetectionCommunicateConstant;
|
||||||
|
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 io.netty.channel.Channel;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测通讯工具类
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @version 1.0
|
||||||
|
* @data 2025/4/15 15:24
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class DetectionCommunicateUtil {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测是否存在连接的源、设备通讯的模块通道
|
||||||
|
* 有则强行关闭
|
||||||
|
*/
|
||||||
|
public static void checkCommunicateChannel(PreDetectionParam param) {
|
||||||
|
Channel channelSource = SocketManager.getChannelByUserId(param.getUserPageId() + DetectionCommunicateConstant.SOURCE);
|
||||||
|
Channel channelDev = SocketManager.getChannelByUserId(param.getUserPageId() + DetectionCommunicateConstant.DEV);
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// SocketManager.removeUser(param.getUserPageId() + DetectionCommunicateConstant.SOURCE);
|
||||||
|
// SocketManager.removeUser(param.getUserPageId() + DetectionCommunicateConstant.DEV);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比对式-检测是否存在已有的Socket通道,有则强行关闭
|
||||||
|
*
|
||||||
|
* @param loginName
|
||||||
|
*/
|
||||||
|
// 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());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
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.util.socket.websocket.WebServiceManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: cdf
|
||||||
|
* @CreateTime: 2025-01-02
|
||||||
|
* @Description: 工具类
|
||||||
|
*/
|
||||||
|
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";
|
||||||
|
|
||||||
|
public final static String END_TAG = "_End";
|
||||||
|
|
||||||
|
public final static String STEP_TAG = "&&";
|
||||||
|
|
||||||
|
public final static String SPLIT_TAG = "_";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出检测
|
||||||
|
*/
|
||||||
|
public static void quitSend(PreDetectionParam param) {
|
||||||
|
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||||
|
socketMsg.setRequestId(SourceOperateCodeEnum.QUITE.getValue());
|
||||||
|
socketMsg.setOperateCode(SourceOperateCodeEnum.QUIT_INIT_03.getValue());
|
||||||
|
SocketManager.sendMsg(param.getUserPageId() + DEV_TAG, JSON.toJSONString(socketMsg));
|
||||||
|
WebServiceManager.removePreDetectionParam(param.getUserPageId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭源连接
|
||||||
|
*/
|
||||||
|
public static void quitSendSource(PreDetectionParam param) {
|
||||||
|
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||||
|
socketMsg.setRequestId(SourceOperateCodeEnum.QUITE_SOURCE.getValue());
|
||||||
|
socketMsg.setOperateCode(SourceOperateCodeEnum.CLOSE_GATHER.getValue());
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
jsonObject.put("sourceId", param.getSourceId());
|
||||||
|
socketMsg.setData(jsonObject.toJSONString());
|
||||||
|
SocketManager.sendMsg(param.getUserPageId() + SOURCE_TAG, JSON.toJSONString(socketMsg));
|
||||||
|
WebServiceManager.removePreDetectionParam(param.getUserPageId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比对式-退出检测
|
||||||
|
*/
|
||||||
|
public static void contrastSendquit(String loginName, SourceOperateCodeEnum operateCode, boolean isRemoveSocket) {
|
||||||
|
System.out.println("比对式-发送关闭备通讯模块指令。。。。。。。。");
|
||||||
|
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||||
|
socketMsg.setRequestId(SourceOperateCodeEnum.QUITE.getValue());
|
||||||
|
socketMsg.setOperateCode(operateCode.getValue());
|
||||||
|
SocketManager.sendMsg(loginName + CONTRAST_DEV_TAG, JSON.toJSONString(socketMsg));
|
||||||
|
// WebServiceManager.removePreDetectionParam();
|
||||||
|
FormalTestManager.isRemoveSocket = isRemoveSocket;
|
||||||
|
FormalTestManager.currentStep = SourceOperateCodeEnum.QUITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
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.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.po.AdPlan;
|
||||||
|
import com.njcn.gather.plan.pojo.po.AdPlanTestConfig;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.enums.DictDataEnum;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: cdf
|
||||||
|
* @CreateTime: 2025-01-08
|
||||||
|
* @Description: 正式(预)检测对象管理
|
||||||
|
*/
|
||||||
|
public class FormalTestManager {
|
||||||
|
|
||||||
|
// 当前步骤
|
||||||
|
public static SourceOperateCodeEnum currentStep;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key:设备ip,value:当前设备下面的监测点ID(ip_通道号)
|
||||||
|
*/
|
||||||
|
public static Map<String, List<String>> devMapMonitorNum = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有参与检测的被检设备
|
||||||
|
*/
|
||||||
|
public static List<PreDetection> devList = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有参与检测的标准设备
|
||||||
|
*/
|
||||||
|
public static List<PreDetection> standardDevList = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有参与检测的监测点
|
||||||
|
*/
|
||||||
|
public static List<String> monitorIdListComm = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有参与检测的监测点。key:监测点(ip_通道号),value:检测点实体
|
||||||
|
*/
|
||||||
|
public static Map<String, PreDetection.MonitorListDTO> monitorMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于存储所有测点的实时数据
|
||||||
|
*/
|
||||||
|
public static List<DevData> realDataXiList = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key:设备ip,value:装置名称
|
||||||
|
*/
|
||||||
|
public static Map<String, String> devNameMapComm = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key:设备ip,value:装置id
|
||||||
|
*/
|
||||||
|
public static HashBiMap<String, String> devIdMapComm = HashBiMap.create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止触发标识
|
||||||
|
*/
|
||||||
|
public static Boolean stopFlag = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已经暂停后的标识
|
||||||
|
*/
|
||||||
|
public static Boolean hasStopFlag = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂停后的超时计时时间
|
||||||
|
*/
|
||||||
|
public static Integer stopTime = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前正在检测的计划
|
||||||
|
*/
|
||||||
|
public static AdPlan currentTestPlan;
|
||||||
|
|
||||||
|
public static AdPlanTestConfig curretntTestPlanConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前正在检测的模式
|
||||||
|
*/
|
||||||
|
public static PatternEnum patternEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比对式检测-检测项。key为检测项code,value为检测项id
|
||||||
|
*/
|
||||||
|
public static Map<String, String> testItemMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据处理原则
|
||||||
|
*/
|
||||||
|
public static DictDataEnum dataRule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有参与比对式检测的被检设备、标准设备配对关系。key:被检设备ip_通道号,value:标准设备ip_通道号
|
||||||
|
*/
|
||||||
|
public static HashBiMap<String, String> pairsIpMap = HashBiMap.create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有参与比对式检测的被检设备、标准设备配对关系。key:被检设备id_通道号,value:标准设备id_通道号
|
||||||
|
*/
|
||||||
|
public static HashBiMap<String, String> pairsIdMap = HashBiMap.create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被检设备的数据。key:设备ip_通道号,value:DevData数据集合
|
||||||
|
*/
|
||||||
|
public static Map<String, List<DevData>> devDataMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标准设备的数据。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<>();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||||
|
import com.njcn.gather.detection.pojo.vo.SocketDataMsg;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wr
|
||||||
|
* @description
|
||||||
|
* @date 2024/12/11 19:27
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class MsgUtil {
|
||||||
|
|
||||||
|
|
||||||
|
public static SocketDataMsg socketDataMsg(String textMsg) {
|
||||||
|
return JSON.parseObject(textMsg, SocketDataMsg.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将对象转换为 JSON 字符串,并在末尾添加换行符
|
||||||
|
*
|
||||||
|
* @param obj 需要转换的对象
|
||||||
|
* @return 包含换行符的 JSON 字符串
|
||||||
|
*/
|
||||||
|
public static String toJsonWithNewLine(Object obj) {
|
||||||
|
return JSON.toJSONString(obj, SerializerFeature.PrettyFormat) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将对象转换为 JSON 字符串,并在末尾添加换行符(不带格式化)
|
||||||
|
*
|
||||||
|
* @param obj 需要转换的对象
|
||||||
|
* @return 包含换行符的 JSON 字符串
|
||||||
|
*/
|
||||||
|
public static String toJsonWithNewLinePlain(Object obj) {
|
||||||
|
return JSON.toJSONString(obj) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param socketDataMsg
|
||||||
|
* @param devMap
|
||||||
|
* @param type 0.装置 1.监测点
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String msgToWebData(SocketDataMsg socketDataMsg, Map<String, String> devMap, Integer type) {
|
||||||
|
String data = socketDataMsg.getData();
|
||||||
|
if (StrUtil.isNotBlank(data)) {
|
||||||
|
String[] parts = data.split("_");
|
||||||
|
if (parts.length > 0) {
|
||||||
|
String key = parts[0];
|
||||||
|
String newValue = devMap.get(key);
|
||||||
|
if (newValue != null) {
|
||||||
|
if (type == 0) {
|
||||||
|
socketDataMsg.setData(newValue);
|
||||||
|
} else {
|
||||||
|
socketDataMsg.setData(newValue + "_" + parts[1] + "路");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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] + "路\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取消息
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
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, hongawen
|
||||||
|
* @Date: 2024/12/11 13:04
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class SocketManager {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SocketConnectionConfig socketConnectionConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key为userId(xxx_Source、xxx_Dev),value为channel
|
||||||
|
*/
|
||||||
|
private static final Map<String, Channel> socketSessions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key为userId(xxx_Source、xxx_Dev),value为group
|
||||||
|
*/
|
||||||
|
private static final Map<String, NioEventLoopGroup> socketGroup = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static void addUser(String userId, Channel channel) {
|
||||||
|
socketSessions.put(userId, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addGroup(String userId, NioEventLoopGroup group) {
|
||||||
|
socketGroup.put(userId, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeUser(String userId) {
|
||||||
|
Channel channel = socketSessions.get(userId);
|
||||||
|
if (ObjectUtil.isNotNull(channel)) {
|
||||||
|
try {
|
||||||
|
channel.close().sync();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
NioEventLoopGroup eventExecutors = socketGroup.get(userId);
|
||||||
|
if (ObjectUtil.isNotNull(eventExecutors)) {
|
||||||
|
eventExecutors.shutdownGracefully();
|
||||||
|
System.out.println(userId + "__" + channel.id() + "关闭了客户端");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
socketSessions.remove(userId);
|
||||||
|
socketGroup.remove(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Channel getChannelByUserId(String userId) {
|
||||||
|
return socketSessions.get(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NioEventLoopGroup getGroupByUserId(String userId) {
|
||||||
|
return socketGroup.get(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMsg(String userId, String msg) {
|
||||||
|
Channel channel = socketSessions.get(userId);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key:大类型code value:对应小项数量(成功一个后减一)
|
||||||
|
*/
|
||||||
|
private static Map<String, Long> targetMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储所有检测小项
|
||||||
|
*/
|
||||||
|
private static List<SourceIssue> sourceIssueList = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于存储pq_script_checkdata表里Value_Type与树字典表code
|
||||||
|
*/
|
||||||
|
public static Map<String, String> valueTypeMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于存储每个测试小项超时时长key key:检测项 value:时间秒
|
||||||
|
*/
|
||||||
|
public static volatile Map<Integer, Long> clockMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于存储比对式测试时间。
|
||||||
|
*/
|
||||||
|
public static volatile Map<DataSourceEnum, Long> contrastClockMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
public static void addSourceList(List<SourceIssue> sList) {
|
||||||
|
sourceIssueList = sList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<SourceIssue> getSourceList() {
|
||||||
|
return sourceIssueList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void delSource(Integer index) {
|
||||||
|
sourceIssueList.removeIf(s -> index.equals(s.getIndex()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void delSourceTarget(String sourceTag) {
|
||||||
|
targetMap.remove(sourceTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void initMap(Map<String, Long> map) {
|
||||||
|
targetMap = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addTargetMap(String scriptType, Long count) {
|
||||||
|
targetMap.put(scriptType, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Long getSourceTarget(String scriptType) {
|
||||||
|
return targetMap.get(scriptType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket;
|
||||||
|
|
||||||
|
import com.njcn.gather.detection.pojo.dto.DevXiNumData;
|
||||||
|
import com.njcn.gather.detection.pojo.vo.CoefficientVO;
|
||||||
|
import com.njcn.gather.device.pojo.vo.PreDetection;
|
||||||
|
import com.njcn.gather.script.pojo.po.SourceIssue;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: cdf
|
||||||
|
* @CreateTime: 2025-01-05
|
||||||
|
* @Description: 系数校验管理器
|
||||||
|
*/
|
||||||
|
public class XiNumberManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储大电压,小电压源控制脚本
|
||||||
|
*/
|
||||||
|
public static List<SourceIssue> xiSourceIssueList = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储校验系数的装置ip,校验完一个则删除一个
|
||||||
|
*/
|
||||||
|
public static List<String> devXiList = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最开始存储装置通道原始系数,后续存储大电压计算出来的系数。key为装置ip
|
||||||
|
*/
|
||||||
|
public static Map<String, DevXiNumData> devXiNumDataMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储所有测点的小电压系数
|
||||||
|
*/
|
||||||
|
public static Map<String, DevXiNumData> smallDevXiNumDataMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系数校验步数计数器 0.大电压 1.小电压 2.小电压 3.大电压
|
||||||
|
*/
|
||||||
|
public static Integer stepNumber = 0;
|
||||||
|
|
||||||
|
|
||||||
|
// 存放大电压、小电压、大电流、小电流的数值
|
||||||
|
public static List<CoefficientVO.DevParameter> devParameterList = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 因为只支持单台装置获取系数,用于记录未获取到系数的装置,获取到一个删除一个
|
||||||
|
*/
|
||||||
|
public static List<PreDetection> xiDevList = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.cilent;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||||
|
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.concurrent.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
log.info("心跳处理器开始清理资源 - 设备类型: {}", handlerType);
|
||||||
|
shutdownExecutorGracefully();
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动心跳定时任务
|
||||||
|
* 每10秒发送一次心跳包,3秒后开始执行
|
||||||
|
*
|
||||||
|
* @param ctx Netty的通道上下文对象
|
||||||
|
*/
|
||||||
|
private void scheduleHeartbeat(ChannelHandlerContext ctx) {
|
||||||
|
heartbeatFuture = heartbeatExecutor.scheduleAtFixedRate(() -> {
|
||||||
|
if (ctx.channel().isActive()) {
|
||||||
|
// 发送心跳包
|
||||||
|
SocketMsg<String> msg = new SocketMsg<>();
|
||||||
|
msg.setRequestId("yxt");
|
||||||
|
msg.setOperateCode(SourceOperateCodeEnum.HEARTBEAT.getValue());
|
||||||
|
msg.setData("");
|
||||||
|
ctx.channel().writeAndFlush(JSON.toJSONString(msg) + "\n");
|
||||||
|
|
||||||
|
log.debug("心跳发送 - 设备类型: {}, 时间: {}", handlerType, LocalDateTime.now());
|
||||||
|
consecutiveHeartbeatMisses++;
|
||||||
|
if (consecutiveHeartbeatMisses >= MAX_HEARTBEAT_MISSES) {
|
||||||
|
// 连续三次未收到心跳响应,异步处理断开逻辑,避免阻塞心跳线程
|
||||||
|
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 || FormalTestManager.currentStep == SourceOperateCodeEnum.RECORD_WAVE_STEP2) {
|
||||||
|
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)) {
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,506 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.cilent;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
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.websocket.WebServiceManager;
|
||||||
|
import io.netty.bootstrap.Bootstrap;
|
||||||
|
import io.netty.channel.*;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||||
|
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||||
|
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.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
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class NettyClient {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SocketSourceResponseService socketSourceResponseService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SocketDevResponseService socketDevResponseService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SocketContrastResponseService socketContrastResponseService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静态实例,用于保持向后兼容
|
||||||
|
*/
|
||||||
|
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 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.cilent;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
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;
|
||||||
|
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 java.time.LocalDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2025-07-25
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class NettyContrastClientHandler extends SimpleChannelInboundHandler<String> {
|
||||||
|
|
||||||
|
|
||||||
|
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.CONTRAST_DEV_TAG);
|
||||||
|
if (Objects.nonNull(channel)) {
|
||||||
|
try {
|
||||||
|
channel.close().sync();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SocketManager.addUser(param.getUserPageId() + CnSocketUtil.CONTRAST_DEV_TAG, ctx.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws InterruptedException {
|
||||||
|
log.info("contrastdevhandler接收server端数据: {}", msg);
|
||||||
|
try {
|
||||||
|
socketContrastResponseService.deal(param, msg);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理服务端消息异常", e);
|
||||||
|
if (FormalTestManager.currentStep == SourceOperateCodeEnum.RECORD_WAVE_STEP1 || FormalTestManager.currentStep == SourceOperateCodeEnum.RECORD_WAVE_STEP2) {
|
||||||
|
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_03, true);
|
||||||
|
} else {
|
||||||
|
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
System.out.println("与通信模块端断线");
|
||||||
|
ctx.close();
|
||||||
|
SocketManager.removeUser(param.getUserPageId() + CnSocketUtil.CONTRAST_DEV_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户事件的回调方法(自定义事件用于心跳机制)
|
||||||
|
*
|
||||||
|
* @param ctx
|
||||||
|
* @param evt
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
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.isRemoveSocket) {
|
||||||
|
//实时数据
|
||||||
|
if (SocketManager.contrastClockMap.get(DataSourceEnum.REAL_DATA) >= 60) {
|
||||||
|
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, false);
|
||||||
|
System.out.println("超时处理-----》" + "实时数据已超时----------------关闭");
|
||||||
|
timeoutSend();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||||
|
System.out.println("有通道准备接入" + ctx.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
System.out.println("IOException caught: There was an I/O error.");
|
||||||
|
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) {
|
||||||
|
System.out.println("ProtocolException caught: Invalid protocol message.");
|
||||||
|
} else {
|
||||||
|
// 处理其他类型的异常
|
||||||
|
System.out.println("Unknown exception caught: " + cause.getMessage());
|
||||||
|
WebServiceManager.sendDetectionErrorMessage(param.getUserPageId(), SourceOperateCodeEnum.DEVICE_ERROR);
|
||||||
|
}
|
||||||
|
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, true);
|
||||||
|
// socketContrastResponseService.backCheckState(param);
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收数据超时处理
|
||||||
|
*/
|
||||||
|
private void timeoutSend() {
|
||||||
|
// 向前端推送超时消息
|
||||||
|
SocketDataMsg webSend = new SocketDataMsg();
|
||||||
|
webSend.setCode(SourceResponseCodeEnum.RECEIVE_DATA_TIME_OUT.getCode());
|
||||||
|
WebServiceManager.sendMsg(param.getUserPageId(), MsgUtil.msgToWebData(webSend, FormalTestManager.devNameMapComm, 0));
|
||||||
|
|
||||||
|
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(webSend));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
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;
|
||||||
|
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.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;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
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 java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 通道上下文
|
||||||
|
* @throws Exception 连接异常
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
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) {
|
||||||
|
log.error("关闭通道异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 注册新的连接到用户管理器
|
||||||
|
SocketManager.addUser(param.getUserPageId() + CnSocketUtil.DEV_TAG, ctx.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当通道断开时的清理工作
|
||||||
|
* <p>关闭连接,清理用户映射,退出源设备发送</p>
|
||||||
|
*
|
||||||
|
* @param ctx 通道上下文
|
||||||
|
* @throws Exception 关闭异常
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
log.warn("设备通讯客户端断线");
|
||||||
|
ctx.close();
|
||||||
|
SocketManager.removeUser(param.getUserPageId() + CnSocketUtil.DEV_TAG);
|
||||||
|
CnSocketUtil.quitSendSource(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理从服务端接收到的消息
|
||||||
|
* <p>将消息交给SocketDevResponseService进行具体处理</p>
|
||||||
|
*
|
||||||
|
* @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) {
|
||||||
|
// 检查是否为读取空闲事件(心跳超时)
|
||||||
|
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) {
|
||||||
|
log.info("有通道准备接入: {}", ctx.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异常捕获处理
|
||||||
|
* <p>捕获并处理各种类型的异常,执行清理工作</p>
|
||||||
|
*
|
||||||
|
* @param ctx 通道上下文
|
||||||
|
* @param cause 异常原因
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
log.error("捕获到设备服务异常", cause);
|
||||||
|
handleSpecificException(cause);
|
||||||
|
// 统一清理工作
|
||||||
|
CnSocketUtil.quitSend(param);
|
||||||
|
CnSocketUtil.quitSendSource(param);
|
||||||
|
socketResponseService.backCheckState(param);
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理特定类型的异常
|
||||||
|
* <p>根据异常类型进行相应的日志记录和错误通知</p>
|
||||||
|
*
|
||||||
|
* @param cause 异常对象
|
||||||
|
*/
|
||||||
|
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 = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.cilent;
|
||||||
|
|
||||||
|
import com.njcn.gather.detection.handler.SocketSourceResponseService;
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源设备Netty客户端通道处理器
|
||||||
|
* 负责处理程控源设备的Socket通信
|
||||||
|
*
|
||||||
|
* @author wr
|
||||||
|
* @since 2024/12/10
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class NettySourceClientHandler extends SimpleChannelInboundHandler<String> {
|
||||||
|
|
||||||
|
/** 检测参数对象,包含用户页面ID等信息 */
|
||||||
|
private final PreDetectionParam webUser;
|
||||||
|
/** 源设备响应处理服务 */
|
||||||
|
private final SocketSourceResponseService sourceResponseService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通道激活回调,将通道注册到SocketManager
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
// 验证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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通道断开回调,清理资源
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
String userId = webUser != null ? webUser.getUserPageId() : "unknown";
|
||||||
|
log.warn("源通讯客户端断线, channelId: {}, userId: {}", ctx.channel().id(), userId);
|
||||||
|
// 关闭通道连接
|
||||||
|
ctx.close();
|
||||||
|
// 从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) {
|
||||||
|
// 记录处理器添加事件,用于调试
|
||||||
|
String userId = webUser != null ? webUser.getUserPageId() : "unknown";
|
||||||
|
log.debug("源设备通道准备接入, channelId: {}, userId: {}", ctx.channel().id(), userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
String userId = webUser != null ? webUser.getUserPageId() : "unknown";
|
||||||
|
String channelId = ctx.channel().id().toString();
|
||||||
|
|
||||||
|
// 根据异常类型进行分类处理和日志记录
|
||||||
|
if (cause instanceof ConnectException) {
|
||||||
|
// 连接异常:网络连接失败
|
||||||
|
log.error("连接源设备Socket服务端异常, channelId: {}, userId: {}", channelId, userId, cause);
|
||||||
|
} else if (cause instanceof IOException) {
|
||||||
|
// 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) {
|
||||||
|
// 超时异常:通信响应超时
|
||||||
|
log.warn("源设备通信超时, channelId: {}, userId: {}", channelId, userId, cause);
|
||||||
|
} else if (cause instanceof ProtocolException) {
|
||||||
|
// 协议异常:数据格式不符合协议规范
|
||||||
|
log.error("源设备协议异常, channelId: {}, userId: {}", channelId, userId, cause);
|
||||||
|
} else {
|
||||||
|
// 其他未知异常
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.service;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||||
|
import com.njcn.gather.detection.pojo.vo.SocketDataMsg;
|
||||||
|
import com.njcn.gather.detection.util.socket.MsgUtil;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 客户端业务处理
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/10 14:18
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class DevNettyServerHandler extends SimpleChannelInboundHandler<String> {
|
||||||
|
|
||||||
|
private final String DEV = "_Dev";
|
||||||
|
private final String source = "_Source";
|
||||||
|
|
||||||
|
private final String stepTag = "&&";
|
||||||
|
private final String stepBegin = "_Start";
|
||||||
|
private final String stepEnd = "_End";
|
||||||
|
|
||||||
|
|
||||||
|
public static final DevNettyServerHandler INSTANCE = new DevNettyServerHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 当通道进行连接时推送消息
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/10 14:19
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
System.out.println("服务端监听到" + ctx.channel().id() + "连接");
|
||||||
|
super.channelActive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理消息信息
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void channelRead0(ChannelHandlerContext ctx, String msg) {
|
||||||
|
System.out.println(ctx.channel().id() + "NettyServer服务端接收到客户端消息:" + msg);
|
||||||
|
Channel channel = ctx.channel();
|
||||||
|
SocketDataMsg socketDataMsg = MsgUtil.socketDataMsg(msg);
|
||||||
|
String[] tem = socketDataMsg.getRequestId().split(stepTag);
|
||||||
|
SourceOperateCodeEnum sourceOperateCodeEnum = SourceOperateCodeEnum.getDictDataEnumByCode(tem[0]);
|
||||||
|
|
||||||
|
switch (Objects.requireNonNull(sourceOperateCodeEnum)) {
|
||||||
|
//设备通讯校验
|
||||||
|
case YJC_SBTXJY:
|
||||||
|
String aa = "{\"requestId\":\"yjc_sbtxjy\",\"operateCode\":\"INIT_GATHER$01\",\"data\":null,\"code\":10201}";
|
||||||
|
String bb = "{\"requestId\":\"yjc_sbtxjy\",\"operateCode\":\"INIT_GATHER$01\",\"data\":null,\"code\":10201}";
|
||||||
|
String cc = "{\"requestId\":\"yjc_sbtxjy\",\"operateCode\":\"INIT_GATHER$01\",\"data\":\"192.168.1.237_1\",\"code\":10200}";
|
||||||
|
channel.writeAndFlush(aa+"\n");
|
||||||
|
channel.writeAndFlush(bb+"\n");
|
||||||
|
channel.writeAndFlush(cc+"\n");
|
||||||
|
break;
|
||||||
|
//协议校验
|
||||||
|
case YJC_XYJY:
|
||||||
|
String dd="{\"requestId\":\"yjc_xyjy\",\"operateCode\":\"INIT_GATHER$02\",\"data\":null,\"code\":10201}";
|
||||||
|
String ee = "{\"requestId\":\"yjc_xyjy\",\"operateCode\":\"INIT_GATHER$02\",\"data\":\"192.168.1.237_1\",\"code\":10200}";
|
||||||
|
channel.writeAndFlush(dd+"\n");
|
||||||
|
channel.writeAndFlush(ee+"\n");
|
||||||
|
|
||||||
|
String ff = "{\"requestId\":\"yjc_xyjy\",\"operateCode\":\"INIT_GATHER$03\",\"data\":null,\"code\":10201}";
|
||||||
|
String gg ="{\"requestId\":\"yjc_xyjy\",\"operateCode\":\"INIT_GATHER$03\",\"data\":\"192.168.1.237_1\",\"code\":10200}";
|
||||||
|
channel.writeAndFlush(ff+"\n");
|
||||||
|
channel.writeAndFlush(gg+"\n");
|
||||||
|
break;
|
||||||
|
//相序校验
|
||||||
|
case YJC_XUJY:
|
||||||
|
String hh = "{\"requestId\":\"YJC_xujy\",\"operateCode\":\"DATA_REQUEST$02\",\"data\":null,\"code\":10201}";
|
||||||
|
String ii = "{\"requestId\":\"YJC_xujy\",\"operateCode\":\"DATA_REQUEST$02\",\"data\":\"{\\\"Time\\\":\\\"2025-02-14T15:09:06.004\\\",\\\"ID\\\":\\\"192.168.1.237_1\\\",\\\"result\\\":false,\\\"SqlData\\\":[{\\\"type\\\":\\\"real\\\",\\\"desc\\\":\\\"VRMS\\\",\\\"list\\\":{\\\"A\\\":\\\"59.969920\\\",\\\"B\\\":\\\"49.963360\\\",\\\"C\\\":\\\"39.971470\\\",\\\"T\\\":null}},{\\\"type\\\":\\\"real\\\",\\\"desc\\\":\\\"VA\\\",\\\"list\\\":{\\\"A\\\":\\\"0.000000\\\",\\\"B\\\":\\\"-119.864000\\\",\\\"C\\\":\\\"120.052000\\\",\\\"T\\\":null}},{\\\"type\\\":\\\"real\\\",\\\"desc\\\":\\\"IRMS\\\",\\\"list\\\":{\\\"A\\\":\\\"1.000979\\\",\\\"B\\\":\\\"1.998939\\\",\\\"C\\\":\\\"3.000807\\\",\\\"T\\\":null}},{\\\"type\\\":\\\"real\\\",\\\"desc\\\":\\\"IA\\\",\\\"list\\\":{\\\"A\\\":\\\"-0.038000\\\",\\\"B\\\":\\\"-120.052000\\\",\\\"C\\\":\\\"119.941000\\\",\\\"T\\\":null}}],\\\"SqlDataHarm\\\":[]}\",\"code\":10200}\n";
|
||||||
|
channel.writeAndFlush(hh+"\n");
|
||||||
|
channel.writeAndFlush(ii+"\n");
|
||||||
|
break;
|
||||||
|
//正式检测
|
||||||
|
case FORMAL_REAL:
|
||||||
|
|
||||||
|
break;
|
||||||
|
//系数校验
|
||||||
|
case Coefficient_Check:
|
||||||
|
String a = "{\"requestId\":\"Coefficient_Check\",\"operateCode\":\"DATA_CHNFACTOR$01\",\"data\":null,\"code\":10201}";
|
||||||
|
String b = "{\"requestId\":\"Coefficient_Check\",\"operateCode\":\"DATA_CHNFACTOR$01\",\"data\":\"[{\\\"uMonitorPoint\\\":0,\\\"f\\\":{\\\"Ua_gain\\\":9999,\\\"Ub_gain\\\":10008,\\\"Uc_gain\\\":10000,\\\"U0_gain\\\":10000,\\\"Ia_gain\\\":9997,\\\"Ib_gain\\\":9997,\\\"Ic_gain\\\":10001,\\\"I0_gain\\\":10000,\\\"Uab_gain\\\":9999,\\\"Ubc_gain\\\":10008,\\\"Uca_gain\\\":10000}}]\",\"code\":10200}";
|
||||||
|
channel.writeAndFlush(a+"\n");
|
||||||
|
channel.writeAndFlush(b+"\n");
|
||||||
|
|
||||||
|
switch (Objects.requireNonNull(sourceOperateCodeEnum)) {
|
||||||
|
case DATA_CHNFACTOR$01:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
break;
|
||||||
|
//退出关闭
|
||||||
|
case QUITE:
|
||||||
|
// quitDeal(socketDataMsg, param, msg);
|
||||||
|
break;
|
||||||
|
case YXT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||||
|
System.out.println("有新连接加入了++++......" + ctx.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
cause.printStackTrace();
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
// Client disconnected, close the server-side channel too
|
||||||
|
System.out.println("NettyServer服务端监听到" + ctx.channel().id() + "关闭连接");
|
||||||
|
ctx.close(); // This will trigger the close event on the server side
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户事件的回调方法(自定义事件用于心跳机制)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
|
//空闲状态的事件
|
||||||
|
// if (evt instanceof IdleStateEvent) {
|
||||||
|
// IdleStateEvent event = (IdleStateEvent) evt;
|
||||||
|
// System.out.println(event.state() + ">>>" + ctx.channel().id());
|
||||||
|
// //已经10秒钟没有读时间了
|
||||||
|
// if (event.state().equals(IdleState.READER_IDLE)){
|
||||||
|
// // 心跳包丢失,10秒没有收到客户端心跳 (断开连接)
|
||||||
|
// ctx.channel().close().sync();
|
||||||
|
// System.out.println("已与 "+ctx.channel().remoteAddress()+" 断开连接");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.service;
|
||||||
|
|
||||||
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.ServerSocketChannel;
|
||||||
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
|
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||||
|
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||||
|
import io.netty.handler.codec.string.StringDecoder;
|
||||||
|
import io.netty.handler.codec.string.StringEncoder;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: NettyServer 心跳检测服务端
|
||||||
|
*
|
||||||
|
* Netty心跳检测与断线重连
|
||||||
|
* 需求:
|
||||||
|
* 1、客户端利用空闲状态给服务端发送心跳ping命令,保持长连接不被关闭;
|
||||||
|
* 2、服务端如果超过指定的时间没有收到客户端心跳,则关闭连接;
|
||||||
|
* 3、服务端关闭连接触发客户端的channelInactive方法,在此方法中进行重连;
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/10 14:18
|
||||||
|
*/
|
||||||
|
public class NettyServer {
|
||||||
|
|
||||||
|
public static final int port = 8574;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
NettyServer nettyServer = new NettyServer();
|
||||||
|
nettyServer.runSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void sourceMain(String[] args) {
|
||||||
|
NettyServer nettyServer = new NettyServer();
|
||||||
|
nettyServer.runDev();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void runSource() {
|
||||||
|
NioEventLoopGroup boss = new NioEventLoopGroup(1);
|
||||||
|
NioEventLoopGroup work = new NioEventLoopGroup();
|
||||||
|
try {
|
||||||
|
ServerBootstrap bootstrap = new ServerBootstrap().group(boss, work);
|
||||||
|
bootstrap.channel(NioServerSocketChannel.class)
|
||||||
|
//这个处理器可以不写
|
||||||
|
.handler(new ChannelInitializer<ServerSocketChannel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(ServerSocketChannel ch) {
|
||||||
|
System.out.println("源通讯服务正在启动中......");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//业务处理
|
||||||
|
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(NioSocketChannel ch) {
|
||||||
|
ch.pipeline()
|
||||||
|
//空闲状态的handler
|
||||||
|
// 添加LineBasedFrameDecoder来按行分割数据
|
||||||
|
.addLast(new LineBasedFrameDecoder(10240*2))
|
||||||
|
.addLast(new StringDecoder(CharsetUtil.UTF_8))
|
||||||
|
.addLast(new StringEncoder(CharsetUtil.UTF_8))
|
||||||
|
.addLast(new DevNettyServerHandler());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ChannelFuture future = bootstrap.bind(port).sync();
|
||||||
|
|
||||||
|
future.addListener(f -> {
|
||||||
|
if (future.isSuccess()) {
|
||||||
|
System.out.println("源通讯服务启动成功");
|
||||||
|
} else {
|
||||||
|
System.out.println("源通讯服务启动失败");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
future.channel().closeFuture().sync();
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
boss.shutdownGracefully();
|
||||||
|
work.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void runDev() {
|
||||||
|
NioEventLoopGroup boss = new NioEventLoopGroup(1);
|
||||||
|
NioEventLoopGroup work = new NioEventLoopGroup();
|
||||||
|
try {
|
||||||
|
ServerBootstrap bootstrap = new ServerBootstrap().group(boss, work);
|
||||||
|
bootstrap.channel(NioServerSocketChannel.class)
|
||||||
|
//这个处理器可以不写
|
||||||
|
.handler(new ChannelInitializer<ServerSocketChannel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(ServerSocketChannel ch) {
|
||||||
|
System.out.println("设备通讯服务正在启动中......");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//业务处理
|
||||||
|
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(NioSocketChannel ch) {
|
||||||
|
ch.pipeline()
|
||||||
|
//空闲状态的handler
|
||||||
|
// 添加LineBasedFrameDecoder来按行分割数据
|
||||||
|
.addLast(new LineBasedFrameDecoder(10240*2))
|
||||||
|
.addLast(new StringDecoder(CharsetUtil.UTF_8))
|
||||||
|
.addLast(new StringEncoder(CharsetUtil.UTF_8))
|
||||||
|
.addLast(new SourceNettyServerHandler());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ChannelFuture future = bootstrap.bind(port).sync();
|
||||||
|
|
||||||
|
future.addListener(f -> {
|
||||||
|
if (future.isSuccess()) {
|
||||||
|
System.out.println("设备通讯服务启动成功");
|
||||||
|
} else {
|
||||||
|
System.out.println("设备通讯服务启动失败");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
future.channel().closeFuture().sync();
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
boss.shutdownGracefully();
|
||||||
|
work.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.service;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||||
|
import io.netty.handler.codec.string.StringDecoder;
|
||||||
|
import io.netty.handler.codec.string.StringEncoder;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 服务端初始化配置
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/10 14:18
|
||||||
|
*/
|
||||||
|
public class NettyServerChannelInitializer extends ChannelInitializer<NioSocketChannel> {
|
||||||
|
|
||||||
|
public static final NettyServerChannelInitializer INSTANCE = new NettyServerChannelInitializer();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initChannel(NioSocketChannel ch) {
|
||||||
|
System.out.println("初始化一次888888888888888888888888");
|
||||||
|
ch.pipeline()
|
||||||
|
//空闲状态的处理器
|
||||||
|
// .addLast(new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS))
|
||||||
|
.addLast(new StringDecoder(CharsetUtil.UTF_8))
|
||||||
|
.addLast(new StringEncoder(CharsetUtil.UTF_8))
|
||||||
|
.addLast(new DevNettyServerHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.service;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||||
|
import com.njcn.gather.detection.pojo.vo.SocketDataMsg;
|
||||||
|
import com.njcn.gather.detection.util.socket.MsgUtil;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 客户端业务处理
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/10 14:18
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class SourceNettyServerHandler extends SimpleChannelInboundHandler<String> {
|
||||||
|
|
||||||
|
private final String DEV = "_Dev";
|
||||||
|
private final String source = "_Source";
|
||||||
|
|
||||||
|
private final String stepTag = "&&";
|
||||||
|
private final String stepBegin = "_Start";
|
||||||
|
private final String stepEnd = "_End";
|
||||||
|
|
||||||
|
public static final SourceNettyServerHandler INSTANCE = new SourceNettyServerHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 当通道进行连接时推送消息
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/10 14:19
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
System.out.println("服务端监听到" + ctx.channel().id() + "连接");
|
||||||
|
super.channelActive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理消息信息
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void channelRead0(ChannelHandlerContext ctx, String msg) {
|
||||||
|
System.out.println(ctx.channel().id() + "NettyServer服务端接收到客户端消息:" + msg);
|
||||||
|
Channel channel = ctx.channel();
|
||||||
|
SocketDataMsg socketDataMsg = MsgUtil.socketDataMsg(msg);
|
||||||
|
String[] tem = socketDataMsg.getRequestId().split(stepTag);
|
||||||
|
SourceOperateCodeEnum enumByCode = SourceOperateCodeEnum.getDictDataEnumByCode(tem[0]);
|
||||||
|
JSONObject jsonObject1 = new JSONObject();
|
||||||
|
if (ObjectUtil.isNotNull(enumByCode)) {
|
||||||
|
switch (enumByCode) {
|
||||||
|
//源初始化
|
||||||
|
case YJC_YTXJY:
|
||||||
|
String aa = "{\"requestId\":\"yjc_ytxjy\",\"operateCode\":\"INIT_GATHER\",\"data\":null,\"code\":10201}";
|
||||||
|
String bb = "{\"requestId\":\"yjc_ytxjy\",\"operateCode\":\"INIT_GATHER\",\"data\":null,\"code\":10200}";
|
||||||
|
channel.writeAndFlush(aa+"/n");
|
||||||
|
channel.writeAndFlush(bb+"/n");
|
||||||
|
break;
|
||||||
|
//相序检测
|
||||||
|
case YJC_XUJY:
|
||||||
|
String hh = "{\"requestId\":\"YJC_xujy\",\"operateCode\":\"OPER_GATHER\",\"data\":null,\"code\":10201}";
|
||||||
|
String ii = "{\"requestId\":\"YJC_xujy\",\"operateCode\":\"OPER_GATHER\",\"data\":null,\"code\":10200}";
|
||||||
|
channel.writeAndFlush(hh+"\n");
|
||||||
|
channel.writeAndFlush(ii+"\n");
|
||||||
|
break;
|
||||||
|
//正式检测
|
||||||
|
case FORMAL_REAL:
|
||||||
|
|
||||||
|
break;
|
||||||
|
//系数校验
|
||||||
|
case Coefficient_Check:
|
||||||
|
String a = "{\"requestId\":\"Coefficient_Check\",\"operateCode\":\"OPER_GATHER\",\"data\":null,\"code\":10201}";
|
||||||
|
String b= "{\"requestId\":\"Coefficient_Check\",\"operateCode\":\"OPER_GATHER\",\"data\":null,\"code\":10200}";
|
||||||
|
|
||||||
|
break;
|
||||||
|
case QUITE_SOURCE:
|
||||||
|
|
||||||
|
break;
|
||||||
|
case YXT:
|
||||||
|
jsonObject1.put("requestId", "yjc_ytxjy");
|
||||||
|
jsonObject1.put("operateCode", "INIT_GATHER");
|
||||||
|
jsonObject1.put("code", "10201");
|
||||||
|
channel.writeAndFlush(jsonObject1.toJSONString() + '\n');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("未知异常" + enumByCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||||
|
System.out.println("有新连接加入了++++......" + ctx.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
cause.printStackTrace();
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
// Client disconnected, close the server-side channel too
|
||||||
|
System.out.println("NettyServer服务端监听到" + ctx.channel().id() + "关闭连接");
|
||||||
|
ctx.close(); // This will trigger the close event on the server side
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户事件的回调方法(自定义事件用于心跳机制)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
|
//空闲状态的事件
|
||||||
|
// if (evt instanceof IdleStateEvent) {
|
||||||
|
// IdleStateEvent event = (IdleStateEvent) evt;
|
||||||
|
// System.out.println(event.state() + ">>>" + ctx.channel().id());
|
||||||
|
// //已经10秒钟没有读时间了
|
||||||
|
// if (event.state().equals(IdleState.READER_IDLE)){
|
||||||
|
// // 心跳包丢失,10秒没有收到客户端心跳 (断开连接)
|
||||||
|
// ctx.channel().close().sync();
|
||||||
|
// System.out.println("已与 "+ctx.channel().remoteAddress()+" 断开连接");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送结构化消息给指定用户
|
||||||
|
* 消息会被序列化为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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储检测参数(基于用户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,402 @@
|
|||||||
|
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.detection.util.socket.SocketManager;
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
private 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)) {
|
||||||
|
if (!FormalTestManager.isRemoveSocket) {
|
||||||
|
if (FormalTestManager.currentStep == SourceOperateCodeEnum.RECORD_WAVE_STEP1 || FormalTestManager.currentStep == SourceOperateCodeEnum.RECORD_WAVE_STEP2) {
|
||||||
|
CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_03, true);
|
||||||
|
} else if (FormalTestManager.currentStep != SourceOperateCodeEnum.QUITE) {
|
||||||
|
CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, true);
|
||||||
|
} else {
|
||||||
|
SocketManager.removeUser(preDetectionParam.getUserPageId() + CnSocketUtil.CONTRAST_DEV_TAG);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
boolean channelActive = SocketManager.isChannelActive(preDetectionParam.getUserPageId() + CnSocketUtil.CONTRAST_DEV_TAG);
|
||||||
|
if (channelActive) {
|
||||||
|
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服务已销毁");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package com.njcn.gather.device.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
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.exception.BusinessException;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.common.utils.LogUtil;
|
||||||
|
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.monitor.pojo.po.PqMonitor;
|
||||||
|
import com.njcn.gather.type.service.IDevTypeService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.FileUtil;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
|
import io.swagger.annotations.ApiImplicitParams;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024/11/06
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Api(tags = "被检设备")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/pqDev")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PqDevController extends BaseController {
|
||||||
|
|
||||||
|
private final IPqDevService pqDevService;
|
||||||
|
private final IDevTypeService devTypeService;
|
||||||
|
|
||||||
|
@OperateInfo
|
||||||
|
@PostMapping("/list")
|
||||||
|
@ApiOperation("分页查询被检设备")
|
||||||
|
@ApiImplicitParam(name = "queryParam", value = "查询参数", required = true)
|
||||||
|
public HttpResult<Page<PqDevVO>> list(@RequestBody @Validated PqDevParam.QueryParam queryParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("list");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam);
|
||||||
|
Page<PqDevVO> result = pqDevService.listPqDevs(queryParam);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo
|
||||||
|
@GetMapping("/getById")
|
||||||
|
@ApiOperation("根据id查询被检设备")
|
||||||
|
@ApiImplicitParam(name = "id", value = "被检设备id", required = true)
|
||||||
|
public HttpResult<PqDevVO> getById(@RequestParam("id") String id) {
|
||||||
|
String methodDescribe = getMethodDescribe("getById");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询ID为:{}", methodDescribe, id);
|
||||||
|
PqDevVO result = pqDevService.getPqDevById(id);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(operateType = OperateType.ADD)
|
||||||
|
@PostMapping("/add")
|
||||||
|
@ApiOperation("新增被检设备")
|
||||||
|
@ApiImplicitParam(name = "pqDevParam", value = "被检设备", required = true)
|
||||||
|
public HttpResult<Boolean> add(@RequestBody @Validated PqDevParam pqDevParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("add");
|
||||||
|
LogUtil.njcnDebug(log, "{},新增数据为:{}", methodDescribe, pqDevParam);
|
||||||
|
boolean result = pqDevService.addPqDev(pqDevParam);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(operateType = OperateType.UPDATE)
|
||||||
|
@PostMapping("/update")
|
||||||
|
@ApiOperation("修改被检设备")
|
||||||
|
@ApiImplicitParam(name = "updateParam", value = "被检设备", required = true)
|
||||||
|
public HttpResult<Boolean> update(@RequestBody @Validated PqDevParam.UpdateParam updateParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("update");
|
||||||
|
LogUtil.njcnDebug(log, "{},修改数据为:{}", methodDescribe, updateParam);
|
||||||
|
boolean result = pqDevService.updatePqDev(updateParam);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(operateType = OperateType.DELETE)
|
||||||
|
@PostMapping("/delete")
|
||||||
|
@ApiOperation("删除被检设备")
|
||||||
|
@ApiImplicitParam(name = "ids", value = "被检设备id", 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()));
|
||||||
|
boolean result = pqDevService.deletePqDev(param);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DOWNLOAD)
|
||||||
|
@PostMapping("/export")
|
||||||
|
@ApiOperation("批量导出被检设备")
|
||||||
|
@ApiImplicitParam(name = "queryParam", value = "查询参数", required = true)
|
||||||
|
public void exportDev(@RequestBody @Validated PqDevParam.QueryParam queryParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("exportDev");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam);
|
||||||
|
pqDevService.exportDev(queryParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DOWNLOAD)
|
||||||
|
@PostMapping("/downloadTemplate")
|
||||||
|
@ApiOperation("下载被检设备导入文件模板")
|
||||||
|
public void downloadTemplate(@RequestBody PqDevParam pqDevParam) {
|
||||||
|
pqDevService.downloadTemplate(pqDevParam.getPattern());
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPLOAD)
|
||||||
|
@PostMapping(value = "/import")
|
||||||
|
@ApiOperation("批量导入被检设备")
|
||||||
|
@ApiImplicitParams({
|
||||||
|
@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) {
|
||||||
|
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)) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@GetMapping("/listUnbound")
|
||||||
|
@ApiOperation("获取指定模式下所有未绑定的设备")
|
||||||
|
@ApiImplicitParam(name = "pattern", value = "模式id", required = true)
|
||||||
|
public HttpResult<List<Map<String, Object>>> listUnbound(@RequestParam("pattern") String pattern) {
|
||||||
|
String methodDescribe = getMethodDescribe("listUnbound");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, pattern);
|
||||||
|
List<Map<String, Object>> result = pqDevService.listUnbound(pattern);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo
|
||||||
|
@GetMapping("/getSelectOptions")
|
||||||
|
@ApiOperation("根据历史记录信息来获取下拉框内容")
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package com.njcn.gather.device.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
import com.njcn.common.pojo.constant.OperateType;
|
||||||
|
import com.njcn.common.pojo.enums.common.DataStateEnum;
|
||||||
|
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||||
|
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.param.PqStandardDevParam;
|
||||||
|
import com.njcn.gather.device.pojo.po.PqStandardDev;
|
||||||
|
import com.njcn.gather.device.service.IPqStandardDevService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.FileUtil;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2025-07-02
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Api(tags = "标准设备管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/pqStandardDev")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PqStandardDevController extends BaseController {
|
||||||
|
private final IPqStandardDevService pqStandardDevService;
|
||||||
|
|
||||||
|
@OperateInfo
|
||||||
|
@PostMapping("/list")
|
||||||
|
@ApiOperation("分页查询标准设备")
|
||||||
|
@ApiImplicitParam(name = "queryParam", value = "查询参数", required = true)
|
||||||
|
public HttpResult<Page<PqStandardDev>> list(@RequestBody PqStandardDevParam.QueryParam queryParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("list");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam);
|
||||||
|
Page<PqStandardDev> result = pqStandardDevService.listPqStandardDevs(queryParam);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo
|
||||||
|
@GetMapping("/getById")
|
||||||
|
@ApiOperation("根据id查询标准设备")
|
||||||
|
@ApiImplicitParam(name = "id", value = "标准设备id", required = true)
|
||||||
|
public HttpResult<PqStandardDev> getById(@RequestParam("id") String id) {
|
||||||
|
String methodDescribe = getMethodDescribe("getById");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询ID为:{}", methodDescribe, id);
|
||||||
|
PqStandardDev result = pqStandardDevService.getPqStandardDevById(id);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(operateType = OperateType.ADD)
|
||||||
|
@PostMapping("/add")
|
||||||
|
@ApiOperation("新增标准设备")
|
||||||
|
@ApiImplicitParam(name = "param", value = "标准设备", required = true)
|
||||||
|
public HttpResult<Boolean> add(@RequestBody @Validated PqStandardDevParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("add");
|
||||||
|
LogUtil.njcnDebug(log, "{},新增数据为:{}", methodDescribe, param);
|
||||||
|
boolean result = pqStandardDevService.addPqStandardDev(param);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(operateType = OperateType.UPDATE)
|
||||||
|
@PostMapping("/update")
|
||||||
|
@ApiOperation("修改标准设备")
|
||||||
|
@ApiImplicitParam(name = "param", value = "标准设备", required = true)
|
||||||
|
public HttpResult<Boolean> update(@RequestBody @Validated PqStandardDevParam.UpdateParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("update");
|
||||||
|
LogUtil.njcnDebug(log, "{},修改数据为:{}", methodDescribe, param);
|
||||||
|
boolean result = pqStandardDevService.updatePqStandardDev(param);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(operateType = OperateType.DELETE)
|
||||||
|
@PostMapping("/delete")
|
||||||
|
@ApiOperation("删除标准设备")
|
||||||
|
@ApiImplicitParam(name = "ids", value = "标准设备id", required = true)
|
||||||
|
public HttpResult<Boolean> delete(@RequestBody List<String> ids) {
|
||||||
|
String methodDescribe = getMethodDescribe("delete");
|
||||||
|
LogUtil.njcnDebug(log, "{},删除ID数据为:{}", methodDescribe, String.join(StrUtil.COMMA, ids));
|
||||||
|
boolean result = pqStandardDevService.deletePqStandardDevs(ids);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DOWNLOAD)
|
||||||
|
@PostMapping("/export")
|
||||||
|
@ApiOperation("批量导出标准设备")
|
||||||
|
@ApiImplicitParam(name = "queryParam", value = "查询参数", required = true)
|
||||||
|
public void export(@RequestBody PqStandardDevParam.QueryParam queryParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("export");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam);
|
||||||
|
pqStandardDevService.export(queryParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DOWNLOAD)
|
||||||
|
@PostMapping("/downloadTemplate")
|
||||||
|
@ApiOperation("下载标准设备导入文件模板")
|
||||||
|
public void downloadTemplate() {
|
||||||
|
pqStandardDevService.downloadTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPLOAD)
|
||||||
|
@PostMapping("/import")
|
||||||
|
@ApiOperation("导入标准设备")
|
||||||
|
@ApiImplicitParam(name = "file", value = "导入文件", required = true)
|
||||||
|
public HttpResult<Object> importData(@RequestParam("file") MultipartFile file, HttpServletResponse response) {
|
||||||
|
String methodDescribe = getMethodDescribe("importData");
|
||||||
|
LogUtil.njcnDebug(log, "{},导入文件为:{}", methodDescribe, file.getOriginalFilename());
|
||||||
|
boolean fileType = FileUtil.judgeFileIsExcel(file.getOriginalFilename());
|
||||||
|
if (!fileType) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FILE_XLSX_ERROR);
|
||||||
|
}
|
||||||
|
pqStandardDevService.importData(file, response);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo
|
||||||
|
@GetMapping("/getAll")
|
||||||
|
@ApiOperation("获取所有标准设备")
|
||||||
|
public HttpResult<List<PqStandardDev>> getAll() {
|
||||||
|
String methodDescribe = getMethodDescribe("getAll");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询所有数据", methodDescribe);
|
||||||
|
List<PqStandardDev> result = pqStandardDevService.lambdaQuery().eq(PqStandardDev::getState, DataStateEnum.ENABLE.getCode()).list();
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.njcn.gather.device.mapper;
|
||||||
|
|
||||||
|
import com.github.yulichang.base.MPJBaseMapper;
|
||||||
|
import com.njcn.gather.device.pojo.param.PqDevParam;
|
||||||
|
import com.njcn.gather.device.pojo.po.PqDev;
|
||||||
|
import com.njcn.gather.device.pojo.vo.PqDevVO;
|
||||||
|
import com.njcn.gather.device.pojo.vo.PreDetection;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-06
|
||||||
|
*/
|
||||||
|
public interface PqDevMapper extends MPJBaseMapper<PqDev> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据装置id集合获取装置信息
|
||||||
|
* @param devIds 装置id
|
||||||
|
* @return: java.util.List<com.njcn.gather.device.pojo.vo.PreDetection>
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/12 11:46
|
||||||
|
*/
|
||||||
|
List<PreDetection> selectDevInfo(@Param("devIds") List<String> devIds);
|
||||||
|
|
||||||
|
void finishPlan(@Param("planId")String planId);
|
||||||
|
|
||||||
|
void updateReportState(@Param("id")String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改检测计划的检测结果
|
||||||
|
*
|
||||||
|
* @param planId
|
||||||
|
* @param checkResult
|
||||||
|
*/
|
||||||
|
void updatePlanCheckResult(@Param("planId")String planId, @Param("checkResult")Integer checkResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改检测计划的检测状态
|
||||||
|
*
|
||||||
|
* @param planId
|
||||||
|
* @param testState
|
||||||
|
*/
|
||||||
|
void updatePlanTestState(@Param("planId")String planId, @Param("testState")Integer testState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据计划id获取脚本id
|
||||||
|
*
|
||||||
|
* @param planId
|
||||||
|
*/
|
||||||
|
String getScriptIdByPlanId(@Param("planId")String planId);
|
||||||
|
|
||||||
|
List<PqDevVO> selectByQueryParam(PqDevParam.QueryParam param);
|
||||||
|
|
||||||
|
PqDevVO selectByDevId(@Param("devId") String devId);
|
||||||
|
|
||||||
|
List<PqDevVO> listByDevIds(@Param("devIds") List<String> devIds);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.njcn.gather.device.mapper;
|
||||||
|
|
||||||
|
import com.github.yulichang.base.MPJBaseMapper;
|
||||||
|
import com.njcn.gather.device.pojo.po.PqDevSub;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2025-07-02
|
||||||
|
*/
|
||||||
|
public interface PqDevSubMapper extends MPJBaseMapper<PqDevSub> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.njcn.gather.device.mapper;
|
||||||
|
|
||||||
|
import com.github.yulichang.base.MPJBaseMapper;
|
||||||
|
import com.njcn.gather.device.pojo.po.PqStandardDev;
|
||||||
|
import com.njcn.gather.device.pojo.vo.PreDetection;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author makejava
|
||||||
|
* @date 2025-05-09
|
||||||
|
*/
|
||||||
|
public interface PqStandardDevMapper extends MPJBaseMapper<PqStandardDev> {
|
||||||
|
|
||||||
|
List<PreDetection> listStandardDevPreDetection(@Param("ids") List<String> ids);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.njcn.gather.device.mapper.PqDevMapper">
|
||||||
|
|
||||||
|
<!-- 通用查询映射结果 -->
|
||||||
|
<resultMap id="DevResultMap" type="com.njcn.gather.device.pojo.vo.PreDetection">
|
||||||
|
<id column="Id" property="devId"/>
|
||||||
|
<id column="Name" property="devName"/>
|
||||||
|
<id column="IP" property="devIP"/>
|
||||||
|
<result column="Port" property="port"/>
|
||||||
|
<result column="Dev_Type" property="devType"/>
|
||||||
|
<result column="Series" property="devCode"/>
|
||||||
|
<result column="Dev_Key" property="devKey"/>
|
||||||
|
<result column="icdType" property="icdType"/>
|
||||||
|
<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"
|
||||||
|
column="{ devId = Id}"
|
||||||
|
select="com.njcn.gather.monitor.mapper.PqMonitorMapper.selectMonitorInfo"
|
||||||
|
>
|
||||||
|
</collection>
|
||||||
|
</resultMap>
|
||||||
|
<select id="selectDevInfo" resultMap="DevResultMap">
|
||||||
|
SELECT
|
||||||
|
d.Id,
|
||||||
|
d.Name,
|
||||||
|
d.IP,
|
||||||
|
d.Port,
|
||||||
|
t.name as Dev_Type,
|
||||||
|
d.Series,
|
||||||
|
d.Dev_Key,
|
||||||
|
p.name as icdType,
|
||||||
|
t.Dev_Chns,
|
||||||
|
t.Dev_Volt,
|
||||||
|
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
|
||||||
|
inner join pq_icd_path p on t.icd = p.id
|
||||||
|
<where>
|
||||||
|
<if test="devIds !=null and devIds.size()!=0">
|
||||||
|
AND d.Id in
|
||||||
|
<foreach collection="devIds" open="(" close=")" item="item" separator=",">
|
||||||
|
#{item}
|
||||||
|
</foreach>
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="finishPlan">
|
||||||
|
update ad_plan
|
||||||
|
set Test_State = 2
|
||||||
|
where id = #{planId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="updateReportState">
|
||||||
|
update pq_dev
|
||||||
|
set Report_State = 1
|
||||||
|
where id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="updatePlanCheckResult">
|
||||||
|
update ad_plan
|
||||||
|
set Result = #{checkResult}
|
||||||
|
where id = #{planId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="updatePlanTestState">
|
||||||
|
update ad_plan
|
||||||
|
set Test_State = #{testState}
|
||||||
|
where id = #{planId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<select id="getScriptIdByPlanId" resultType="java.lang.String">
|
||||||
|
SELECT Script_Id
|
||||||
|
FROM ad_plan
|
||||||
|
WHERE id = #{planId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByQueryParam" resultType="com.njcn.gather.device.pojo.vo.PqDevVO">
|
||||||
|
SELECT dev.*,dev_sub.* FROM pq_dev dev
|
||||||
|
left JOIN pq_dev_sub dev_sub ON dev.Id = dev_sub.Dev_Id
|
||||||
|
<where>
|
||||||
|
dev.state = 1
|
||||||
|
<if test="planIdList!= null and planIdList.size!=0">
|
||||||
|
AND dev.Plan_Id in
|
||||||
|
<foreach collection="planIdList" open="(" close=")" item="item" separator=",">
|
||||||
|
#{item}
|
||||||
|
</foreach>
|
||||||
|
</if>
|
||||||
|
<if test="name!= null and name!= ''">
|
||||||
|
AND dev.Name LIKE CONCAT('%',#{name},'%')
|
||||||
|
</if>
|
||||||
|
<if test="checkStateList!= null and checkStateList.size()!= 0 ">
|
||||||
|
AND dev_sub.Check_State in
|
||||||
|
<foreach collection="checkStateList" open="(" close=")" item="item" separator=",">
|
||||||
|
#{item}
|
||||||
|
</foreach>
|
||||||
|
</if>
|
||||||
|
<if test="checkResult!= null">
|
||||||
|
AND dev_sub.Check_Result = #{checkResult}
|
||||||
|
</if>
|
||||||
|
<if test="reportState!= null">
|
||||||
|
AND dev_sub.Report_State = #{reportState}
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY dev.Create_Time DESC,dev.Name
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByDevId" resultType="com.njcn.gather.device.pojo.vo.PqDevVO">
|
||||||
|
SELECT dev.*, dev_sub.*
|
||||||
|
FROM pq_dev dev
|
||||||
|
left JOIN pq_dev_sub dev_sub ON dev.Id = dev_sub.Dev_Id
|
||||||
|
WHERE dev.Id = #{devId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listByDevIds" resultType="com.njcn.gather.device.pojo.vo.PqDevVO">
|
||||||
|
SELECT dev.*, dev_sub.*
|
||||||
|
FROM pq_dev dev
|
||||||
|
left JOIN pq_dev_sub dev_sub ON dev.Id = dev_sub.Dev_Id
|
||||||
|
WHERE dev.state = 1
|
||||||
|
<if test="devIds!= null and devIds.size() != 0">
|
||||||
|
AND dev.Id in
|
||||||
|
<foreach collection="devIds" open="(" close=")" item="item" separator=",">
|
||||||
|
#{item}
|
||||||
|
</foreach>
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.njcn.gather.device.mapper.PqDevSubMapper">
|
||||||
|
|
||||||
|
|
||||||
|
</mapper>
|
||||||
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.njcn.gather.device.mapper.PqStandardDevMapper">
|
||||||
|
|
||||||
|
<!-- 通用查询映射结果 -->
|
||||||
|
<resultMap id="standardDevResultMap" type="com.njcn.gather.device.pojo.vo.PreDetection">
|
||||||
|
<id column="Id" property="devId"/>
|
||||||
|
<id column="Name" property="devName"/>
|
||||||
|
<id column="IP" property="devIP"/>
|
||||||
|
<result column="Port" property="port"/>
|
||||||
|
<result column="Dev_Type" property="devType"/>
|
||||||
|
<result column="Series" property="devCode"/>
|
||||||
|
<result column="Dev_Key" property="devKey"/>
|
||||||
|
<result column="icdType" property="icdType"/>
|
||||||
|
<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"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="listStandardDevPreDetection" resultMap="standardDevResultMap">
|
||||||
|
select
|
||||||
|
standard_dev.Id,
|
||||||
|
standard_dev.Name,
|
||||||
|
standard_dev.IP,
|
||||||
|
standard_dev.Port,
|
||||||
|
dev_type.name as Dev_Type,
|
||||||
|
standard_dev.Series,
|
||||||
|
standard_dev.Dev_Key,
|
||||||
|
icd_path.Name as icdType,
|
||||||
|
dev_type.Dev_Chns,
|
||||||
|
dev_type.Dev_Volt,
|
||||||
|
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}
|
||||||
|
</foreach>
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.njcn.gather.device.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-12-12
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum CheckResultEnum {
|
||||||
|
NOT_ACCORD("不符合", 0),
|
||||||
|
ACCORD("符合", 1),
|
||||||
|
UNCHECKED("未检", 2);
|
||||||
|
|
||||||
|
private final Integer value;
|
||||||
|
private final String msg;
|
||||||
|
|
||||||
|
CheckResultEnum(String msg, Integer value) {
|
||||||
|
this.msg = msg;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMsgByValue(Integer value) {
|
||||||
|
for (CheckStateEnum state : CheckStateEnum.values()) {
|
||||||
|
if (state.getValue().equals(value)) {
|
||||||
|
return state.getMsg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.njcn.gather.device.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-12-12
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum CheckStateEnum {
|
||||||
|
UNCHECKED("未检", 0),
|
||||||
|
CHECKING("检测中", 1),
|
||||||
|
CHECKED("检测完成", 2),
|
||||||
|
/**
|
||||||
|
* 检测计划没有该状态,只有未检、检测中、检测完成三种状态。被检设备有这种状态
|
||||||
|
*/
|
||||||
|
DOCUMENTED("归档", 3);
|
||||||
|
|
||||||
|
private final Integer value;
|
||||||
|
private final String msg;
|
||||||
|
|
||||||
|
CheckStateEnum(String msg, Integer value) {
|
||||||
|
this.msg = msg;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMsgByValue(Integer value) {
|
||||||
|
for (CheckStateEnum state : CheckStateEnum.values()) {
|
||||||
|
if (state.getValue().equals(value)) {
|
||||||
|
return state.getMsg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.njcn.gather.device.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-12-13
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum CommonEnum {
|
||||||
|
FATHER_ID("0", "无"),
|
||||||
|
|
||||||
|
NO("0", "否"),
|
||||||
|
YES("1", "是"),
|
||||||
|
|
||||||
|
|
||||||
|
COEFFICIENT_TEST("0","系数校验"),
|
||||||
|
PRE_TEST("1","预检测"),
|
||||||
|
FORMAL_TEST("2","正式检测"),
|
||||||
|
TIME_TEST("3","守时检测"),
|
||||||
|
PHASE_TEST("4","相序检测"),
|
||||||
|
;
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
private String msg;
|
||||||
|
|
||||||
|
CommonEnum(String value, String msg) {
|
||||||
|
this.value = value;
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.njcn.gather.device.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-12-12
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum DevDocumentStateEnum {
|
||||||
|
UNDOCUMENTED("未归档", 0),
|
||||||
|
DOCUMENTED("归档", 1);
|
||||||
|
|
||||||
|
private final Integer value;
|
||||||
|
private final String msg;
|
||||||
|
|
||||||
|
DevDocumentStateEnum(String msg, Integer value) {
|
||||||
|
this.msg = msg;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.njcn.gather.device.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-12-12
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum DevReportStateEnum {
|
||||||
|
NOT_GENERATED("未生成", 0),
|
||||||
|
GENERATED("已生成", 1),
|
||||||
|
GENERATED_UPLOADED("已生成且已上传", 3),
|
||||||
|
UNCHECKED("未检", 2);
|
||||||
|
|
||||||
|
private final Integer value;
|
||||||
|
private final String msg;
|
||||||
|
|
||||||
|
DevReportStateEnum(String msg, Integer value) {
|
||||||
|
this.msg = msg;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMsgByValue(Integer value) {
|
||||||
|
for (CheckStateEnum state : CheckStateEnum.values()) {
|
||||||
|
if (state.getValue().equals(value)) {
|
||||||
|
return state.getMsg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.njcn.gather.device.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-12-14
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum FactorCheckResultEnum {
|
||||||
|
NOT_QUALIFY(0, "不合格"),
|
||||||
|
QUALIFY(1, "合格"),
|
||||||
|
UNKNOWN(2, "/");
|
||||||
|
|
||||||
|
private final Integer value;
|
||||||
|
private final String msg;
|
||||||
|
|
||||||
|
FactorCheckResultEnum(Integer value, String msg) {
|
||||||
|
this.value = value;
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMsgByValue(Integer value) {
|
||||||
|
for (FactorCheckResultEnum e : FactorCheckResultEnum.values()) {
|
||||||
|
if (e.getValue().equals(value)) {
|
||||||
|
return e.getMsg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.njcn.gather.device.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-12-12
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum PatternEnum {
|
||||||
|
SIMULATE("Simulate", "模拟式"),
|
||||||
|
DIGITAL("Digital", "数字式"),
|
||||||
|
CONTRAST("Contrast", "比对式");
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
private String msg;
|
||||||
|
|
||||||
|
PatternEnum(String value, String msg) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.njcn.gather.device.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-12-14
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum TimeCheckResultEnum {
|
||||||
|
NOT_QUALIFY(0, "不合格"),
|
||||||
|
QUALIFY(1, "合格"),
|
||||||
|
UNKNOWN(2, "/");
|
||||||
|
|
||||||
|
private final Integer value;
|
||||||
|
private final String msg;
|
||||||
|
|
||||||
|
TimeCheckResultEnum(Integer value, String msg) {
|
||||||
|
this.value = value;
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMsgByValue(Integer value) {
|
||||||
|
for (TimeCheckResultEnum e : TimeCheckResultEnum.values()) {
|
||||||
|
if (e.getValue().equals(value)) {
|
||||||
|
return e.getMsg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
package com.njcn.gather.device.pojo.param;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.constant.PatternRegex;
|
||||||
|
import com.njcn.gather.monitor.pojo.param.PqMonitorParam;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024/11/06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PqDevParam {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "名称", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.NAME_NOT_BLANK)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备模式,字典表(数字、模拟、比对)", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.PATTERN_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.PATTERN_FORMAT_ERROR)
|
||||||
|
private String pattern;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备类型", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.DEV_TYPE_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.DEV_TYPE_FORMAT_ERROR)
|
||||||
|
private String devType;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备厂家,字典表", required = true)
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "出厂日期", required = true)
|
||||||
|
private String createDate;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "装置编号", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.FACTORYNO_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.DEV_CREATE_ID_REGEX, message = DetectionValidMessage.DEV_CREATE_ID_FORMAT_ERROR)
|
||||||
|
private String createId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "固件版本", required = true)
|
||||||
|
private String hardwareVersion;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "软件版本", required = true)
|
||||||
|
private String softwareVersion;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "通讯协议", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.PROTOCOL_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.PROTOCOL_FORMAT_ERROR)
|
||||||
|
private String protocol;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "IP地址", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.IP_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.IP_REGEX, message = DetectionValidMessage.IP_FORMAT_ERROR)
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "端口号", required = true)
|
||||||
|
@NotNull(message = DetectionValidMessage.PORT_NOT_NULL)
|
||||||
|
@Range(min = 1, max = 65535, message = DetectionValidMessage.PORT_RANGE_ERROR)
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "装置是否为加密版本", required = true)
|
||||||
|
@NotNull(message = DetectionValidMessage.ENCRYPTION_NOT_NULL)
|
||||||
|
@Min(value = 0, message = DetectionValidMessage.ENCRYPTION_FLAG_FORMAT_ERROR)
|
||||||
|
@Max(value = 1, message = DetectionValidMessage.ENCRYPTION_FLAG_FORMAT_ERROR)
|
||||||
|
private Integer encryptionFlag;
|
||||||
|
|
||||||
|
@ApiModelProperty("装置识别码(3ds加密)")
|
||||||
|
private String series;
|
||||||
|
|
||||||
|
@ApiModelProperty("装置秘钥(3ds加密)")
|
||||||
|
private String devKey;
|
||||||
|
|
||||||
|
@ApiModelProperty("样品编号")
|
||||||
|
private String sampleId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "送样日期")
|
||||||
|
@DateTimeStrValid(message = DetectionValidMessage.ARRIVE_DATE_FORMAT_ERROR)
|
||||||
|
private String arrivedDate;
|
||||||
|
|
||||||
|
@ApiModelProperty("所属地市名称")
|
||||||
|
private String cityName;
|
||||||
|
|
||||||
|
@ApiModelProperty("所属供电公司名称")
|
||||||
|
private String gdName;
|
||||||
|
|
||||||
|
@ApiModelProperty("所属电站名称")
|
||||||
|
private String subName;
|
||||||
|
|
||||||
|
@ApiModelProperty("报告路径")
|
||||||
|
private String reportPath;
|
||||||
|
|
||||||
|
// @ApiModelProperty("设备关键信息二维码")
|
||||||
|
// private String qrCode;
|
||||||
|
|
||||||
|
// @ApiModelProperty(value = "检测次数,默认为0", required = true)
|
||||||
|
// @NotNull(message = DetectionValidMessage.RECHECK_NUM_NOT_NULL)
|
||||||
|
// @Min(value = 0, message = DetectionValidMessage.RECHECK_NUM_FORMAT_ERROR)
|
||||||
|
// private Integer reCheckNum;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否支持系数校准")
|
||||||
|
private Integer factorFlag;
|
||||||
|
|
||||||
|
@ApiModelProperty("监测点台账列表")
|
||||||
|
@Valid
|
||||||
|
private List<PqMonitorParam> monitorList;
|
||||||
|
|
||||||
|
@ApiModelProperty("icdId")
|
||||||
|
private String icdId;
|
||||||
|
|
||||||
|
@ApiModelProperty("预投计划")
|
||||||
|
private String preinvestmentPlan;
|
||||||
|
|
||||||
|
@ApiModelProperty("委托方")
|
||||||
|
private String delegate;
|
||||||
|
|
||||||
|
@ApiModelProperty("被检通道")
|
||||||
|
private String inspectChannel;
|
||||||
|
|
||||||
|
@ApiModelProperty("投运日期")
|
||||||
|
@DateTimeStrValid(message = DetectionValidMessage.OPERATION_DATE_FORMAT_ERROR)
|
||||||
|
private String operationDate;
|
||||||
|
|
||||||
|
@ApiModelProperty("定验日期")
|
||||||
|
@DateTimeStrValid(message = DetectionValidMessage.INSPECT_DATE_FORMAT_ERROR)
|
||||||
|
private String inspectDate;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否为导入设备")
|
||||||
|
private Integer importFlag;
|
||||||
|
/**
|
||||||
|
* 更新操作实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class UpdateParam extends PqDevParam {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "id", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.ID_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.ID_FORMAT_ERROR)
|
||||||
|
private String id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class QueryParam extends BaseParam {
|
||||||
|
@ApiModelProperty("名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备模式,字典表(数字、模拟、比对)")
|
||||||
|
private String pattern;
|
||||||
|
|
||||||
|
@ApiModelProperty("设备厂家")
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
@ApiModelProperty("检测计划ID")
|
||||||
|
private List<String> planIdList;
|
||||||
|
|
||||||
|
@ApiModelProperty("检测状态列表")
|
||||||
|
private List<
|
||||||
|
@Min(value = 0, message = DetectionValidMessage.CHECK_STATE_FORMAT_ERROR)
|
||||||
|
@Max(value = 3, message = DetectionValidMessage.CHECK_STATE_FORMAT_ERROR) Integer> checkStateList;
|
||||||
|
|
||||||
|
@ApiModelProperty("检测结果")
|
||||||
|
@Min(value = 0, message = DetectionValidMessage.CHECK_RESULT_FORMAT_ERROR)
|
||||||
|
@Max(value = 2, message = DetectionValidMessage.CHECK_RESULT_FORMAT_ERROR)
|
||||||
|
private Integer checkResult;
|
||||||
|
|
||||||
|
@ApiModelProperty("报告状态")
|
||||||
|
@Min(value = 0, message = DetectionValidMessage.REPORT_STATE_FORMAT_ERROR)
|
||||||
|
@Max(value = 2, message = DetectionValidMessage.REPORT_STATE_FORMAT_ERROR)
|
||||||
|
private Integer reportState;
|
||||||
|
|
||||||
|
@ApiModelProperty("所属地市名称")
|
||||||
|
private String cityName;
|
||||||
|
|
||||||
|
@ApiModelProperty("所属供电公司名称")
|
||||||
|
private String gdName;
|
||||||
|
|
||||||
|
@ApiModelProperty("所属电站名称")
|
||||||
|
private String subName;
|
||||||
|
|
||||||
|
@ApiModelProperty("地市")
|
||||||
|
private String region;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否分配")
|
||||||
|
private Integer assign;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DeleteParam {
|
||||||
|
@ApiModelProperty(value = "ids")
|
||||||
|
private List<@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.ID_FORMAT_ERROR) String> ids;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备模式,字典表(数字、模拟、比对)", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.PATTERN_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.PATTERN_FORMAT_ERROR)
|
||||||
|
private String pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class BindPlanParam {
|
||||||
|
@ApiModelProperty("检测计划ID")
|
||||||
|
@NotBlank(message = DetectionValidMessage.PLAN_ID_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.PLAN_ID_FORMAT_ERROR)
|
||||||
|
private String planId;
|
||||||
|
|
||||||
|
@ApiModelProperty("被检设备ID列表")
|
||||||
|
@NotNull(message = DetectionValidMessage.PQ_DEV_IDS_NOT_NULL)
|
||||||
|
private List<String> devIds;
|
||||||
|
|
||||||
|
@ApiModelProperty("0-解绑、1-绑定")
|
||||||
|
private Integer bindFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.njcn.gather.device.pojo.param;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.constant.PatternRegex;
|
||||||
|
import com.njcn.gather.pojo.constant.DetectionValidMessage;
|
||||||
|
import com.njcn.web.pojo.param.BaseParam;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.hibernate.validator.constraints.Range;
|
||||||
|
|
||||||
|
import javax.validation.constraints.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2025-05-09
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PqStandardDevParam {
|
||||||
|
@ApiModelProperty(value = "名称", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.NAME_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.DEV_NAME_REGEX, message = DetectionValidMessage.NAME_FORMAT_ERROR)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备类型", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.DEV_TYPE_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.DEV_TYPE_FORMAT_ERROR)
|
||||||
|
private String devType;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备厂家", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.MANUFACTURER_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.MANUFACTURER_FORMAT_ERROR)
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "通信协议", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.PROTOCOL_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.PROTOCOL_FORMAT_ERROR)
|
||||||
|
private String protocol;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "ip地址", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.IP_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.IP_REGEX, message = DetectionValidMessage.IP_FORMAT_ERROR)
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "端口", required = true)
|
||||||
|
@NotNull(message = DetectionValidMessage.PORT_NOT_NULL)
|
||||||
|
@Range(min = 1, max = 65535, message = DetectionValidMessage.PORT_RANGE_ERROR)
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "装置是否为加密版本", required = true)
|
||||||
|
@NotNull(message = DetectionValidMessage.ENCRYPTION_NOT_NULL)
|
||||||
|
@Min(value = 0, message = DetectionValidMessage.ENCRYPTION_FLAG_FORMAT_ERROR)
|
||||||
|
@Max(value = 1, message = DetectionValidMessage.ENCRYPTION_FLAG_FORMAT_ERROR)
|
||||||
|
private Integer encryptionFlag;
|
||||||
|
|
||||||
|
@ApiModelProperty("识别码(3ds加密)")
|
||||||
|
private String series;
|
||||||
|
|
||||||
|
@ApiModelProperty("秘钥(3ds加密)")
|
||||||
|
private String devKey;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "可检通道", required = true)
|
||||||
|
private String inspectChannel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class QueryParam extends BaseParam {
|
||||||
|
@ApiModelProperty(value = "名称", required = true)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备厂家", required = true)
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备类型", required = true)
|
||||||
|
private String devType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新操作实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class UpdateParam extends PqStandardDevParam {
|
||||||
|
@ApiModelProperty(value = "标准设备id", required = true)
|
||||||
|
@NotBlank(message = DetectionValidMessage.ID_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.ID_FORMAT_ERROR)
|
||||||
|
private String id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package com.njcn.gather.device.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
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
|
||||||
|
* @date 2024/11/06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("pq_dev")
|
||||||
|
public class PqDev extends BaseEntity implements Serializable {
|
||||||
|
private static final long serialVersionUID = -45763424394344208L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键装置序号ID
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备模式,字典表(数字、模拟、比对)
|
||||||
|
*/
|
||||||
|
private String pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备类型,字典表
|
||||||
|
*/
|
||||||
|
private String devType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备厂家,字典表
|
||||||
|
*/
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出厂日期
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
@JsonDeserialize(using = LocalDateDeserializer.class)
|
||||||
|
@JsonSerialize(using = LocalDateSerializer.class)
|
||||||
|
private LocalDate createDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备序列号
|
||||||
|
*/
|
||||||
|
private String createId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 固件版本
|
||||||
|
*/
|
||||||
|
private String hardwareVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 软件版本
|
||||||
|
*/
|
||||||
|
private String softwareVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通讯协议,字典表(MMS、PODIF)
|
||||||
|
*/
|
||||||
|
private String protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP地址
|
||||||
|
*/
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 端口号
|
||||||
|
*/
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置是否为加密版本
|
||||||
|
*/
|
||||||
|
private Integer encryptionFlag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置识别码(3ds加密)
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.UPDATE)
|
||||||
|
private String series;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置秘钥(3ds加密)
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.UPDATE)
|
||||||
|
private String devKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 样品编号
|
||||||
|
*/
|
||||||
|
private String sampleId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 送样日期
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
@JsonDeserialize(using = LocalDateDeserializer.class)
|
||||||
|
@JsonSerialize(using = LocalDateSerializer.class)
|
||||||
|
private LocalDate arrivedDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属地市名称
|
||||||
|
*/
|
||||||
|
private String cityName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属供电公司名称
|
||||||
|
*/
|
||||||
|
private String gdName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属电站名称
|
||||||
|
*/
|
||||||
|
private String subName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报告路径
|
||||||
|
*/
|
||||||
|
private String reportPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测计划id
|
||||||
|
*/
|
||||||
|
private String planId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否支持系数校准(0:不支持,1:支持)
|
||||||
|
*/
|
||||||
|
private Integer factorFlag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预投计划
|
||||||
|
*/
|
||||||
|
@TableField("Preinvestment_Plan")
|
||||||
|
private String preinvestmentPlan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 委托方
|
||||||
|
*/
|
||||||
|
private String delegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可检通道
|
||||||
|
*/
|
||||||
|
private String inspectChannel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定检日期
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
@JsonDeserialize(using = LocalDateDeserializer.class)
|
||||||
|
@JsonSerialize(using = LocalDateSerializer.class)
|
||||||
|
private LocalDate inspectDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 谐波系统设备id
|
||||||
|
*/
|
||||||
|
private String harmSysId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为导入设备(比对式使用) 0-否 1-是
|
||||||
|
*/
|
||||||
|
private Integer importFlag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态:0-删除 1-正常
|
||||||
|
*/
|
||||||
|
private Integer state;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private List<PqMonitor> monitorList;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.njcn.gather.device.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2025-07-04
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("pq_dev_sub")
|
||||||
|
public class PqDevSub {
|
||||||
|
private static final long serialVersionUID = -93146063424890267L;
|
||||||
|
/**
|
||||||
|
* 关联pq_dev表的id字段
|
||||||
|
*/
|
||||||
|
private String devId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测状态 0-未检、1检测中、2检测完成、3归档
|
||||||
|
*/
|
||||||
|
private Integer checkState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测结果 0不符合、1符合、2未检
|
||||||
|
*/
|
||||||
|
private Integer checkResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报告生成状态 0未生成、1已生成、2未检
|
||||||
|
*/
|
||||||
|
private Integer reportState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测次数
|
||||||
|
*/
|
||||||
|
@TableField(value = "ReCheck_Num")
|
||||||
|
private Integer recheckNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 守时检测结果 0:不合格, 1:合格,2:/表示没有做守时检测
|
||||||
|
*/
|
||||||
|
private Integer timeCheckResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系数校准结果 0:不合格,1:合格,2:/表示没有做系数校准
|
||||||
|
*/
|
||||||
|
private Integer factorCheckResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测人
|
||||||
|
*/
|
||||||
|
private String checkBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测时间
|
||||||
|
*/
|
||||||
|
@TableField("Check_Time")
|
||||||
|
private LocalDateTime checkTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预检测耗时
|
||||||
|
*/
|
||||||
|
private Integer preDetectTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系数校准耗时
|
||||||
|
*/
|
||||||
|
private Integer coefficientTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正式检测耗时
|
||||||
|
*/
|
||||||
|
private Integer formalCheckTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 温度
|
||||||
|
*/
|
||||||
|
private Float temperature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相对湿度
|
||||||
|
*/
|
||||||
|
private Float humidity;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.njcn.gather.device.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.njcn.db.mybatisplus.bo.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author makejava
|
||||||
|
* @date 2025-05-09
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("pq_standard_dev")
|
||||||
|
public class PqStandardDev extends BaseEntity implements Serializable {
|
||||||
|
private static final long serialVersionUID = 932459726326242984L;
|
||||||
|
/**
|
||||||
|
* 标准设备id
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标准设备-名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标准设备-设备类型
|
||||||
|
*/
|
||||||
|
private String devType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标准设备-设备厂家
|
||||||
|
*/
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可检通道(可多选,中间使用英文逗号隔开)
|
||||||
|
*/
|
||||||
|
private String inspectChannel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通信协议
|
||||||
|
*/
|
||||||
|
private String protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP地址
|
||||||
|
*/
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 端口号
|
||||||
|
*/
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否加密(0否、1是)
|
||||||
|
*/
|
||||||
|
private Integer encryptionFlag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 识别码
|
||||||
|
*/
|
||||||
|
private String series;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密钥
|
||||||
|
*/
|
||||||
|
private String devKey;
|
||||||
|
|
||||||
|
private Integer state;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.njcn.gather.device.pojo.vo;
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.excel.annotation.Excel;
|
||||||
|
import com.njcn.common.pojo.constant.PatternRegex;
|
||||||
|
import com.njcn.gather.pojo.constant.DetectionValidMessage;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.Range;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2025-01-15
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CNDevExcel {
|
||||||
|
|
||||||
|
@Excel(name = "预投计划*", width = 20, orderNum = "1")
|
||||||
|
@NotBlank(message = DetectionValidMessage.PREINVESTMENT_PLAN_NOT_BLANK)
|
||||||
|
private String preinvestmentPlan;
|
||||||
|
|
||||||
|
@Excel(name = "装置编号(开始编号-结束编号,编号为数字)*", width = 50, orderNum = "2")
|
||||||
|
@NotBlank(message = DetectionValidMessage.NAME_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.CN_DEV_NAME_REGEX_IMPORT, message = DetectionValidMessage.CN_DEV_NAME_FORMAT_ERROR)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Excel(name = "设备类型*", width = 20, orderNum = "3")
|
||||||
|
@NotBlank(message = DetectionValidMessage.DEV_TYPE_NOT_BLANK)
|
||||||
|
private String devType;
|
||||||
|
|
||||||
|
@Excel(name = "通讯协议*", width = 15, orderNum = "4")
|
||||||
|
@NotBlank(message = DetectionValidMessage.PROTOCOL_NOT_BLANK)
|
||||||
|
private String protocol;
|
||||||
|
|
||||||
|
@Excel(name = "是否加密*", width = 20, replace = {"否_0", "是_1"}, orderNum = "5")
|
||||||
|
@NotNull(message = DetectionValidMessage.ENCRYPTION_NOT_NULL)
|
||||||
|
private Integer encryptionFlag;
|
||||||
|
|
||||||
|
@Excel(name = "识别码(当加密时必填)", width = 30, orderNum = "6")
|
||||||
|
private String series;
|
||||||
|
|
||||||
|
@Excel(name = "秘钥(当加密时必填)", width = 30, orderNum = "7")
|
||||||
|
private String devKey;
|
||||||
|
|
||||||
|
@Excel(name = "是否支持系数校准*", width = 25, replace = {"否_0", "是_1"}, orderNum = "8")
|
||||||
|
private Integer factorFlag;
|
||||||
|
|
||||||
|
@Excel(name = "IP地址*", width = 20, orderNum = "9")
|
||||||
|
@NotBlank(message = DetectionValidMessage.IP_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.IP_REGEX, message = DetectionValidMessage.IP_FORMAT_ERROR)
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
@Excel(name = "端口号*", width = 15, orderNum = "10")
|
||||||
|
@NotNull(message = DetectionValidMessage.PORT_NOT_NULL)
|
||||||
|
@Range(min = 1, max = 65535, message = DetectionValidMessage.PORT_RANGE_ERROR)
|
||||||
|
private Integer port;
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.njcn.gather.device.pojo.vo;
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.excel.annotation.Excel;
|
||||||
|
import cn.afterturn.easypoi.excel.annotation.ExcelCollection;
|
||||||
|
import com.njcn.common.pojo.constant.PatternRegex;
|
||||||
|
import com.njcn.gather.monitor.pojo.vo.PqMonitorExcel;
|
||||||
|
import com.njcn.gather.pojo.constant.DetectionValidMessage;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.Range;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2025-05-07
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ContrastDevExcel implements Serializable {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Excel(name = "所属地市*", width = 30, needMerge = true, orderNum = "1")
|
||||||
|
private String cityName;
|
||||||
|
|
||||||
|
@Excel(name = "所属供电公司*", width = 30, needMerge = true, orderNum = "2")
|
||||||
|
private String gdName;
|
||||||
|
|
||||||
|
@Excel(name = "所属电站*", width = 30, needMerge = true, orderNum = "3")
|
||||||
|
private String subName;
|
||||||
|
|
||||||
|
@Excel(name = "设备名称*", width = 20, needMerge = true, orderNum = "4")
|
||||||
|
@NotBlank(message = DetectionValidMessage.NAME_NOT_BLANK)
|
||||||
|
@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)
|
||||||
|
private String createId;
|
||||||
|
|
||||||
|
@Excel(name = "设备类型*", width = 20, needMerge = true, orderNum = "6")
|
||||||
|
@NotBlank(message = DetectionValidMessage.DEV_TYPE_NOT_BLANK)
|
||||||
|
private String devType;
|
||||||
|
|
||||||
|
@Excel(name = "设备厂家*", width = 20, needMerge = true, orderNum = "7")
|
||||||
|
@NotBlank(message = DetectionValidMessage.MANUFACTURER_NOT_BLANK)
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
// @Excel(name = "被检通道", width = 20, needMerge = true, orderNum = "8")
|
||||||
|
// private String inspectChannel;
|
||||||
|
|
||||||
|
@Excel(name = "固件版本", width = 15, needMerge = true, orderNum = "8")
|
||||||
|
private String hardwareVersion;
|
||||||
|
|
||||||
|
@Excel(name = "软件版本", width = 15, needMerge = true, orderNum = "9")
|
||||||
|
private String softwareVersion;
|
||||||
|
|
||||||
|
@Excel(name = "通讯协议*", width = 15, needMerge = true, orderNum = "10")
|
||||||
|
@NotBlank(message = DetectionValidMessage.PROTOCOL_NOT_BLANK)
|
||||||
|
private String protocol;
|
||||||
|
|
||||||
|
@Excel(name = "是否加密*", width = 20, needMerge = true, replace = {"否_0", "是_1"}, orderNum = "11")
|
||||||
|
@NotNull(message = DetectionValidMessage.ENCRYPTION_NOT_NULL)
|
||||||
|
private Integer encryptionFlag;
|
||||||
|
|
||||||
|
@Excel(name = "识别码(当加密时必填)", width = 30, needMerge = true, orderNum = "12")
|
||||||
|
private String series;
|
||||||
|
|
||||||
|
@Excel(name = "秘钥(当加密时必填)", width = 30, needMerge = true, orderNum = "13")
|
||||||
|
private String devKey;
|
||||||
|
|
||||||
|
@Excel(name = "IP地址*", width = 20, needMerge = true, orderNum = "14")
|
||||||
|
@NotBlank(message = DetectionValidMessage.IP_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.IP_REGEX, message = DetectionValidMessage.IP_FORMAT_ERROR)
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
@Excel(name = "端口号*", width = 15, needMerge = true, orderNum = "15")
|
||||||
|
@NotNull(message = DetectionValidMessage.PORT_NOT_NULL)
|
||||||
|
@Range(min = 1, max = 65535, message = DetectionValidMessage.PORT_RANGE_ERROR)
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
@Excel(name = "投运日期(yyyy-MM-dd)*", width = 30, needMerge = true, orderNum = "16", format = "yyyy-MM-dd")
|
||||||
|
@NotNull(message = DetectionValidMessage.CREATE_DATE_NOT_NULL)
|
||||||
|
private LocalDate createDate;
|
||||||
|
|
||||||
|
@Excel(name = "定检日期(yyyy-MM-dd)*", width = 30, needMerge = true, orderNum = "17", format = "yyyy-MM-dd")
|
||||||
|
@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)
|
||||||
|
private String harmSysId;
|
||||||
|
|
||||||
|
@ExcelCollection(name = "监测点信息", orderNum = "19")
|
||||||
|
private List<PqMonitorExcel> pqMonitorExcelList;
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package com.njcn.gather.device.pojo.vo;
|
||||||
|
|
||||||
|
import com.njcn.gather.device.pojo.po.PqDev;
|
||||||
|
import com.njcn.gather.monitor.pojo.po.PqMonitor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-12-13
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PqDevVO extends PqDev {
|
||||||
|
|
||||||
|
private Integer devChns;
|
||||||
|
|
||||||
|
private Double devVolt;
|
||||||
|
|
||||||
|
private Double devCurr;
|
||||||
|
|
||||||
|
private List<PqMonitor> monitorList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测状态 0-未检、1检测中、2检测完成、3归档
|
||||||
|
*/
|
||||||
|
private Integer checkState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测结果 0不符合、1符合、2未检
|
||||||
|
*/
|
||||||
|
private Integer checkResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报告生成状态 0未生成、1已生成、2未检
|
||||||
|
*/
|
||||||
|
private Integer reportState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测次数
|
||||||
|
*/
|
||||||
|
private Integer recheckNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 守时检测结果 0:不合格, 1:合格,2:/表示没有做守时检测
|
||||||
|
*/
|
||||||
|
private Integer timeCheckResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系数校准结果 0:不合格,1:合格,2:/表示没有做系数校准
|
||||||
|
*/
|
||||||
|
private Integer factorCheckResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实时数据结果 0:不合格,1:合格,2:未检
|
||||||
|
*/
|
||||||
|
private Integer realtimeResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计数据结果 0:不合格,1:合格,2:未检
|
||||||
|
*/
|
||||||
|
private Integer statisticsResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 录波数据结果 0:不合格,1:合格,2:未检
|
||||||
|
*/
|
||||||
|
private Integer recordedResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测人
|
||||||
|
*/
|
||||||
|
private String checkBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime checkTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预检测耗时
|
||||||
|
*/
|
||||||
|
private Integer preDetectTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系数校准耗时
|
||||||
|
*/
|
||||||
|
private Integer coefficientTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正式检测耗时
|
||||||
|
*/
|
||||||
|
private Integer formalCheckTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 温度
|
||||||
|
*/
|
||||||
|
private Float temperature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相对湿度
|
||||||
|
*/
|
||||||
|
private Float humidity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定的计划名称
|
||||||
|
*/
|
||||||
|
private String boundPlanName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已经分配。0-未分配、1-已分配、2-所有
|
||||||
|
*/
|
||||||
|
private Integer assign;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.njcn.gather.device.pojo.vo;
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.excel.annotation.Excel;
|
||||||
|
import com.njcn.common.pojo.constant.PatternRegex;
|
||||||
|
import com.njcn.gather.pojo.constant.DetectionValidMessage;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.Range;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2025-07-01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PqStandardDevExcel {
|
||||||
|
|
||||||
|
@Excel(name = "名称*", width = 20, orderNum = "1")
|
||||||
|
@NotBlank(message = DetectionValidMessage.NAME_NOT_BLANK)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Excel(name = "设备类型*", width = 25, orderNum = "2")
|
||||||
|
@NotBlank(message = DetectionValidMessage.DEV_TYPE_NOT_BLANK)
|
||||||
|
private String devType;
|
||||||
|
|
||||||
|
@Excel(name = "设备厂家*", width = 25, orderNum = "3")
|
||||||
|
@NotBlank(message = DetectionValidMessage.MANUFACTURER_NOT_BLANK)
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
@Excel(name = "可检通道*", width = 25, orderNum = "4")
|
||||||
|
@NotBlank(message = DetectionValidMessage.INSPECT_CHANNEL_NOT_BLANK)
|
||||||
|
private String inspectChannel;
|
||||||
|
|
||||||
|
@Excel(name = "通信协议*", width = 25, orderNum = "5")
|
||||||
|
@NotBlank(message = DetectionValidMessage.PROTOCOL_NOT_BLANK)
|
||||||
|
private String protocol;
|
||||||
|
|
||||||
|
@Excel(name = "IP地址*", width = 25, orderNum = "6")
|
||||||
|
@NotBlank(message = DetectionValidMessage.IP_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.IP_REGEX, message = DetectionValidMessage.IP_FORMAT_ERROR)
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
@Excel(name = "端口号*", width = 15, orderNum = "7")
|
||||||
|
@NotNull(message = DetectionValidMessage.PORT_NOT_NULL)
|
||||||
|
@Range(min = 1, max = 65535, message = DetectionValidMessage.PORT_RANGE_ERROR)
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
@Excel(name = "是否加密*", width = 20, replace = {"否_0", "是_1"}, orderNum = "8")
|
||||||
|
@NotNull(message = DetectionValidMessage.ENCRYPTION_NOT_NULL)
|
||||||
|
private Integer encryptionFlag;
|
||||||
|
|
||||||
|
@Excel(name = "识别码(当加密时必填)", width = 30, orderNum = "9")
|
||||||
|
private String series;
|
||||||
|
|
||||||
|
@Excel(name = "秘钥(当加密时必填)", width = 30, orderNum = "10")
|
||||||
|
private String devKey;
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package com.njcn.gather.device.pojo.vo;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import com.njcn.common.utils.EncryptionUtil;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wr
|
||||||
|
* @description 预检测装置报文初始化
|
||||||
|
* @date 2024/12/12 11:21
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class PreDetection {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置id
|
||||||
|
*/
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
private String devId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置ip
|
||||||
|
*/
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
private String devName;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置ip
|
||||||
|
*/
|
||||||
|
@JSONField(name = "devIP")
|
||||||
|
private String devIP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置端口
|
||||||
|
*/
|
||||||
|
@JSONField(name = "port")
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备类型,字典表
|
||||||
|
*/
|
||||||
|
@JSONField(name = "devType")
|
||||||
|
private String devType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* icd设备类型
|
||||||
|
*/
|
||||||
|
@JSONField(name = "icdType")
|
||||||
|
private String icdType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否支持相角。0:不支持,1:支持
|
||||||
|
*/
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
private Integer angle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角型接线时是否使用相别的指标来进行检测,0表示否,1表示是
|
||||||
|
*/
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
private Integer usePhaseIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置识别码(3ds加密)
|
||||||
|
*/
|
||||||
|
@JSONField(name = "devCode")
|
||||||
|
private String devCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装置秘钥(3ds加密)
|
||||||
|
*/
|
||||||
|
@JSONField(name = "devKey")
|
||||||
|
private String devKey;
|
||||||
|
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
private Integer devChns;
|
||||||
|
|
||||||
|
private Double devVolt;
|
||||||
|
|
||||||
|
private Double devCurr;
|
||||||
|
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
private String waveCmd;
|
||||||
|
/**
|
||||||
|
* 监测点信息
|
||||||
|
*/
|
||||||
|
@JSONField(name = "monitorList")
|
||||||
|
private List<MonitorListDTO> monitorList;
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class MonitorListDTO {
|
||||||
|
/**
|
||||||
|
* 监测点id
|
||||||
|
*/
|
||||||
|
@JSONField(name = "lineId")
|
||||||
|
private String lineId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监测点线路号
|
||||||
|
*/
|
||||||
|
@JSONField(name = "line")
|
||||||
|
private Integer line;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pt
|
||||||
|
*/
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
private String ptStr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ct
|
||||||
|
*/
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
private String ctStr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pt
|
||||||
|
*/
|
||||||
|
@JSONField(name = "pt")
|
||||||
|
private Double pt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ct
|
||||||
|
*/
|
||||||
|
@JSONField(name = "ct")
|
||||||
|
private Double ct;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计间隔
|
||||||
|
*/
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
private Integer statInterval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接线方式
|
||||||
|
*/
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
private String connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDevKey() {
|
||||||
|
if (StrUtil.isNotBlank(devKey)) {
|
||||||
|
String key = EncryptionUtil.decoderString(1, devKey);
|
||||||
|
if (StrUtil.isNotBlank(key)) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDevCode() {
|
||||||
|
if (StrUtil.isNotBlank(devCode)) {
|
||||||
|
String code = EncryptionUtil.decoderString(1, devCode);
|
||||||
|
if (StrUtil.isNotBlank(code)) {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.njcn.gather.device.pojo.vo;
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.excel.annotation.Excel;
|
||||||
|
import com.njcn.common.pojo.constant.PatternRegex;
|
||||||
|
import com.njcn.gather.pojo.constant.DetectionValidMessage;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.Range;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024/11/7
|
||||||
|
* @description 省级平台设备Excel导入导出实体类
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ProvinceDevExcel implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Excel(name = "装置编号*", width = 20)
|
||||||
|
@NotBlank(message = DetectionValidMessage.FACTORYNO_NOT_BLANK)
|
||||||
|
private String createId;
|
||||||
|
|
||||||
|
@Excel(name = "设备名称*", width = 20, orderNum = "2")
|
||||||
|
@NotBlank(message = DetectionValidMessage.NAME_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.DEV_NAME_REGEX, message = DetectionValidMessage.NAME_FORMAT_ERROR)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Excel(name = "设备类型*", width = 20, orderNum = "3")
|
||||||
|
@NotBlank(message = DetectionValidMessage.DEV_TYPE_NOT_BLANK)
|
||||||
|
private String devType;
|
||||||
|
|
||||||
|
@Excel(name = "设备厂家*", width = 20, orderNum = "7")
|
||||||
|
@NotBlank(message = DetectionValidMessage.MANUFACTURER_NOT_BLANK)
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
@Excel(name = "出厂日期(yyyy-MM-dd)*", width = 25, format = "yyyy-MM-dd", orderNum = "8")
|
||||||
|
@NotNull(message = DetectionValidMessage.CREATEDATETIME_NOT_NULL)
|
||||||
|
private LocalDate createDate;
|
||||||
|
|
||||||
|
@Excel(name = "固件版本", width = 15, orderNum = "9")
|
||||||
|
private String hardwareVersion;
|
||||||
|
|
||||||
|
@Excel(name = "软件版本", width = 15, orderNum = "10")
|
||||||
|
private String softwareVersion;
|
||||||
|
|
||||||
|
@Excel(name = "通讯协议*", width = 15, orderNum = "11")
|
||||||
|
@NotBlank(message = DetectionValidMessage.PROTOCOL_NOT_BLANK)
|
||||||
|
private String protocol;
|
||||||
|
|
||||||
|
@Excel(name = "是否加密*", width = 20, replace = {"否_0", "是_1"}, orderNum = "12")
|
||||||
|
@NotNull(message = DetectionValidMessage.ENCRYPTION_NOT_NULL)
|
||||||
|
private Integer encryptionFlag;
|
||||||
|
|
||||||
|
@Excel(name = "识别码(当加密时必填)", width = 30, orderNum = "13")
|
||||||
|
private String series;
|
||||||
|
|
||||||
|
@Excel(name = "秘钥(当加密时必填)", width = 30, orderNum = "14")
|
||||||
|
private String devKey;
|
||||||
|
|
||||||
|
// @Excel(name = "是否支持系数校准*", width = 25, replace = {"否_0", "是_1"}, orderNum = "15")
|
||||||
|
// private Integer factorFlag;
|
||||||
|
|
||||||
|
@Excel(name = "IP地址*", width = 20, orderNum = "15")
|
||||||
|
@NotBlank(message = DetectionValidMessage.IP_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.IP_REGEX, message = DetectionValidMessage.IP_FORMAT_ERROR)
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
@Excel(name = "端口号*", width = 15, orderNum = "16")
|
||||||
|
@NotNull(message = DetectionValidMessage.PORT_NOT_NULL)
|
||||||
|
@Range(min = 1, max = 65535, message = DetectionValidMessage.PORT_RANGE_ERROR)
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
@Excel(name = "样品编号", width = 20, orderNum = "17")
|
||||||
|
private String sampleId;
|
||||||
|
|
||||||
|
@Excel(name = "送样日期(yyyy-MM-dd)", width = 25, format = "yyyy-MM-dd", orderNum = "18")
|
||||||
|
private LocalDate arrivedDate;
|
||||||
|
|
||||||
|
@Excel(name="委托方", width = 40, orderNum = "19")
|
||||||
|
private String delegate;
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
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.gather.device.pojo.enums.TimeCheckResultEnum;
|
||||||
|
import com.njcn.gather.device.pojo.param.PqDevParam;
|
||||||
|
import com.njcn.gather.device.pojo.po.PqDev;
|
||||||
|
import com.njcn.gather.device.pojo.vo.*;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024/11/06
|
||||||
|
*/
|
||||||
|
public interface IPqDevService extends IService<PqDev> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询被检设备列表
|
||||||
|
*
|
||||||
|
* @param queryParam 查询参数
|
||||||
|
* @return 分页数据,包含被检设备列表
|
||||||
|
*/
|
||||||
|
Page<PqDevVO> listPqDevs(PqDevParam.QueryParam queryParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增被检设备信息
|
||||||
|
*
|
||||||
|
* @param pqDevParam 被检设备信息
|
||||||
|
* @return 新增成功返回true,否则返回false
|
||||||
|
*/
|
||||||
|
boolean addPqDev(PqDevParam pqDevParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改被检设备信息
|
||||||
|
*
|
||||||
|
* @param updateParam 被检设备信息
|
||||||
|
* @return 修改成功返回true,否则返回false
|
||||||
|
*/
|
||||||
|
boolean updatePqDev(PqDevParam.UpdateParam updateParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除被检设备信息
|
||||||
|
*
|
||||||
|
* @param param 被检设备信息
|
||||||
|
* @return 删除成功返回true,否则返回false
|
||||||
|
*/
|
||||||
|
boolean deletePqDev(PqDevParam.DeleteParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新被检设备守时检测结果
|
||||||
|
*
|
||||||
|
* @param ids 被检设备id列表
|
||||||
|
* @param result 守时检测结果
|
||||||
|
* @return 更新成功返回true,否则返回false
|
||||||
|
*/
|
||||||
|
boolean updatePqDevTimeCheckResult(List<String> ids, TimeCheckResultEnum result);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有未绑定的设备
|
||||||
|
*
|
||||||
|
* @param pattern 模式Id
|
||||||
|
* @return 未绑定的设备列表
|
||||||
|
*/
|
||||||
|
List<Map<String, Object>> listUnbound(String pattern);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据计划id获取绑定的设备
|
||||||
|
*
|
||||||
|
* @param param 计划id
|
||||||
|
* @return 绑定的设备列表
|
||||||
|
*/
|
||||||
|
List<PqDevVO> listByPlanId(PqDevParam.QueryParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定计划
|
||||||
|
*
|
||||||
|
* @param planId 计划id
|
||||||
|
* @param devIds 设备id列表
|
||||||
|
* @return 绑定成功返回true,否则返回false
|
||||||
|
*/
|
||||||
|
Integer bind(String planId, List<String> devIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据id获取被检设备信息
|
||||||
|
*
|
||||||
|
* @param id 被检设备id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
PqDevVO getPqDevById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取装置信息和装置下监测点信息
|
||||||
|
*
|
||||||
|
* @param devIds
|
||||||
|
* @return: java.util.List<com.njcn.gather.device.pojo.vo.PreDetection>
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/12 15:50
|
||||||
|
*/
|
||||||
|
List<PreDetection> getDevInfo(@Param("devIds") List<String> devIds);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正式监测完成,修改中断状态
|
||||||
|
*
|
||||||
|
* @param ids
|
||||||
|
* @param adType
|
||||||
|
* @param code
|
||||||
|
* @param userId
|
||||||
|
* @param temperature
|
||||||
|
* @param humidity
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
long countUnReportDev(String planId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据计划id列表获取设备列表
|
||||||
|
*
|
||||||
|
* @param planIds
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<PqDev> listByPlanIds(List<String> planIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出设备数据
|
||||||
|
*
|
||||||
|
* @param queryParam
|
||||||
|
*/
|
||||||
|
void exportDev(PqDevParam.QueryParam queryParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载模板文件
|
||||||
|
*
|
||||||
|
* @param patternId 模式Id
|
||||||
|
*/
|
||||||
|
void downloadTemplate(String patternId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入设备数据
|
||||||
|
*
|
||||||
|
* @param file 上传的文件
|
||||||
|
* @param patternId 模式Id
|
||||||
|
* @param planId 计划Id
|
||||||
|
* @param response 响应
|
||||||
|
*/
|
||||||
|
boolean importDev(MultipartFile file, String patternId, String planId, HttpServletResponse response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入灿能二楼设备数据
|
||||||
|
*
|
||||||
|
* @param file 上传的文件
|
||||||
|
* @param patternId 模式Id
|
||||||
|
* @param planId 计划Id
|
||||||
|
* @param response 响应
|
||||||
|
*/
|
||||||
|
boolean importCNDev(MultipartFile file, String patternId, String planId, HttpServletResponse response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入灿能二楼设备数据
|
||||||
|
*
|
||||||
|
* @param cnDevExcelList
|
||||||
|
* @param patternId
|
||||||
|
* @param planId
|
||||||
|
*/
|
||||||
|
boolean importCNDev(List<CNDevExcel> cnDevExcelList, String patternId, String planId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可视化-灿能二楼设备
|
||||||
|
*
|
||||||
|
* @param pqDevs
|
||||||
|
*/
|
||||||
|
void visualizeCNDev(List<PqDevVO> pqDevs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逆向可视化-灿能二楼设备
|
||||||
|
*
|
||||||
|
* @param pqDevs 设备列表
|
||||||
|
* @param patternId 模式Id
|
||||||
|
*/
|
||||||
|
void reverseVisualizeCNDev(List<PqDev> pqDevs, String patternId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入省级平台设备数据
|
||||||
|
*
|
||||||
|
* @param file 上传的文件
|
||||||
|
* @param patternId 模式Id
|
||||||
|
* @param planId 计划Id
|
||||||
|
* @param response 响应
|
||||||
|
*/
|
||||||
|
boolean importProvinceDev(MultipartFile file, String patternId, String planId, HttpServletResponse response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入省级平台设备数据
|
||||||
|
*
|
||||||
|
* @param proviceDevExcelList
|
||||||
|
* @param patternId
|
||||||
|
* @param planId
|
||||||
|
*/
|
||||||
|
boolean importProvinceDev(List<ProvinceDevExcel> proviceDevExcelList, String patternId, String planId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可视化-省级平台设备
|
||||||
|
*
|
||||||
|
* @param pqDevs
|
||||||
|
*/
|
||||||
|
void visualizeProvinceDev(List<PqDevVO> pqDevs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逆向可视化-省级平台设备
|
||||||
|
*
|
||||||
|
* @param pqDevs 设备列表
|
||||||
|
* @param patternId 模式Id
|
||||||
|
*/
|
||||||
|
void reverseVisualizeProvinceDev(List<PqDev> pqDevs, String patternId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取省级平台设备导出、导出文件模板的下拉列表
|
||||||
|
*
|
||||||
|
* @param startCol 开始列
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<PullDown> getProvinceDevPullDownList(int startCol);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取灿能二楼设备导出、导出文件模板的下拉列表
|
||||||
|
*
|
||||||
|
* @param startCol 开始列
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<PullDown> getCNDevPullDownList(int startCol);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入比对式设备数据
|
||||||
|
*
|
||||||
|
* @param file 上传的文件
|
||||||
|
* @param patternId 模式Id
|
||||||
|
* @param planId 计划Id
|
||||||
|
* @param response 响应
|
||||||
|
*/
|
||||||
|
boolean importContrastDev(MultipartFile file, String patternId, String planId, HttpServletResponse response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入比对式设备数据
|
||||||
|
*
|
||||||
|
* @param contrastDevExcelList
|
||||||
|
* @param patternId
|
||||||
|
*/
|
||||||
|
boolean importContrastDev(List<ContrastDevExcel> contrastDevExcelList, String patternId, String planId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取比对式设备导出、导出文件模板的下拉列表
|
||||||
|
*
|
||||||
|
* @param startCol
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<PullDown> getContrastDevPullDownList(int startCol);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据历史记录来获取下拉框内容
|
||||||
|
*
|
||||||
|
* @param pattern
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Map<String, List<String>> listSelectOptions(String pattern);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取比对式设备导出数据
|
||||||
|
*
|
||||||
|
* @param pqDevVOList 设备id列表
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<ContrastDevExcel> getExportContrastDevData(List<PqDevVO> pqDevVOList);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.njcn.gather.device.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.device.pojo.po.PqDevSub;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2025-07-04
|
||||||
|
*/
|
||||||
|
public interface IPqDevSubService extends IService<PqDevSub> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package com.njcn.gather.device.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.device.pojo.param.PqStandardDevParam;
|
||||||
|
import com.njcn.gather.device.pojo.po.PqStandardDev;
|
||||||
|
import com.njcn.gather.device.pojo.vo.PreDetection;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2025-07-02
|
||||||
|
*/
|
||||||
|
public interface IPqStandardDevService extends IService<PqStandardDev> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询标准设备列表
|
||||||
|
*
|
||||||
|
* @param queryParam 分页查询参数
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Page<PqStandardDev> listPqStandardDevs(PqStandardDevParam.QueryParam queryParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据id查询标准设备
|
||||||
|
*
|
||||||
|
* @param id 设备id
|
||||||
|
* @return 设备对象
|
||||||
|
*/
|
||||||
|
PqStandardDev getPqStandardDevById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增标准设备
|
||||||
|
*
|
||||||
|
* @param param 新增参数
|
||||||
|
* @return 新增成功返回true,失败返回false
|
||||||
|
*/
|
||||||
|
boolean addPqStandardDev(PqStandardDevParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改标准设备
|
||||||
|
*
|
||||||
|
* @param param 修改参数
|
||||||
|
* @return 修改成功返回true,失败返回false
|
||||||
|
*/
|
||||||
|
boolean updatePqStandardDev(PqStandardDevParam.UpdateParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除标准设备
|
||||||
|
*
|
||||||
|
* @param ids
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean deletePqStandardDevs(List<String> ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出标准设备
|
||||||
|
*
|
||||||
|
* @param queryParam
|
||||||
|
*/
|
||||||
|
void export(PqStandardDevParam.QueryParam queryParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下装标准设备导入模板
|
||||||
|
*/
|
||||||
|
void downloadTemplate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入标准设备数据
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
void importData(MultipartFile file, HttpServletResponse response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询出指定计划以关联的标准设备列表
|
||||||
|
*
|
||||||
|
* @param planId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<PqStandardDev> listByPlanId(String planId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询出标准设备所需的检测信息
|
||||||
|
*
|
||||||
|
* @param ids
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<PreDetection> listStandardDevPreDetection(List<String> ids);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询可绑定的标准设备列表
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<PqStandardDev> canBindingList();
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
|||||||
|
package com.njcn.gather.device.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.njcn.gather.device.pojo.po.PqDevSub;
|
||||||
|
import com.njcn.gather.device.mapper.PqDevSubMapper;
|
||||||
|
import com.njcn.gather.device.service.IPqDevSubService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2025-07-04
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PqDevSubServiceImpl extends ServiceImpl<PqDevSubMapper, PqDevSub> implements IPqDevSubService {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,355 @@
|
|||||||
|
package com.njcn.gather.device.service.impl;
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.excel.ExcelImportUtil;
|
||||||
|
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||||
|
import cn.afterturn.easypoi.excel.entity.ImportParams;
|
||||||
|
import cn.afterturn.easypoi.excel.entity.result.ExcelImportResult;
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
|
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.common.pojo.poi.PullDown;
|
||||||
|
import com.njcn.common.utils.EncryptionUtil;
|
||||||
|
import com.njcn.gather.device.mapper.PqStandardDevMapper;
|
||||||
|
import com.njcn.gather.device.pojo.param.PqStandardDevParam;
|
||||||
|
import com.njcn.gather.device.pojo.po.PqStandardDev;
|
||||||
|
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.pojo.po.AdPlan;
|
||||||
|
import com.njcn.gather.plan.pojo.po.AdPlanStandardDev;
|
||||||
|
import com.njcn.gather.plan.service.IAdPlanService;
|
||||||
|
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;
|
||||||
|
import com.njcn.gather.system.dictionary.service.IDictDataService;
|
||||||
|
import com.njcn.gather.system.dictionary.service.IDictTypeService;
|
||||||
|
import com.njcn.gather.type.service.IDevTypeService;
|
||||||
|
import com.njcn.web.factory.PageFactory;
|
||||||
|
import com.njcn.web.utils.ExcelUtil;
|
||||||
|
import com.njcn.web.utils.PoiUtil;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 曹泽辉
|
||||||
|
* @date 2025-07-02
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PqStandardDevServiceImpl extends ServiceImpl<PqStandardDevMapper, PqStandardDev> implements IPqStandardDevService {
|
||||||
|
private final IDevTypeService devTypeService;
|
||||||
|
private final IDictDataService dictDataService;
|
||||||
|
private final IDictTypeService dictTypeService;
|
||||||
|
private final AdPlanStandardDevMapper adPlanStandardDevMapper;
|
||||||
|
private final IAdPlanStandardDevService adPlanStandardDevService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<PqStandardDev> listPqStandardDevs(PqStandardDevParam.QueryParam queryParam) {
|
||||||
|
QueryWrapper<PqStandardDev> wrapper = new QueryWrapper<>();
|
||||||
|
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());
|
||||||
|
return this.page(new Page<>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PqStandardDev getPqStandardDevById(String id) {
|
||||||
|
PqStandardDev standardDev = this.getById(id);
|
||||||
|
if (standardDev.getEncryptionFlag() == 1) {
|
||||||
|
standardDev.setSeries(EncryptionUtil.decoderString(1, standardDev.getSeries()));
|
||||||
|
standardDev.setDevKey(EncryptionUtil.decoderString(1, standardDev.getDevKey()));
|
||||||
|
}
|
||||||
|
return standardDev;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean addPqStandardDev(PqStandardDevParam param) {
|
||||||
|
this.checkRepeat(param, false);
|
||||||
|
PqStandardDev pqStandardDev = BeanUtil.copyProperties(param, PqStandardDev.class);
|
||||||
|
pqStandardDev.setState(DataStateEnum.ENABLE.getCode());
|
||||||
|
this.checkEncryption(pqStandardDev);
|
||||||
|
return this.save(pqStandardDev);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean updatePqStandardDev(PqStandardDevParam.UpdateParam param) {
|
||||||
|
this.checkRepeat(param, true);
|
||||||
|
PqStandardDev pqStandardDev = BeanUtil.copyProperties(param, PqStandardDev.class);
|
||||||
|
this.checkEncryption(pqStandardDev);
|
||||||
|
return this.updateById(pqStandardDev);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean deletePqStandardDevs(List<String> ids) {
|
||||||
|
LambdaUpdateWrapper<PqStandardDev> wrapper = new LambdaUpdateWrapper();
|
||||||
|
wrapper.set(PqStandardDev::getState, DataStateEnum.DELETED.getCode())
|
||||||
|
.in(PqStandardDev::getId, ids);
|
||||||
|
return this.update(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void export(PqStandardDevParam.QueryParam queryParam) {
|
||||||
|
QueryWrapper<PqStandardDev> wrapper = new QueryWrapper<>();
|
||||||
|
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());
|
||||||
|
List<PqStandardDev> pqStandardDevs = this.list(wrapper);
|
||||||
|
this.visualizeStandardDev(pqStandardDevs);
|
||||||
|
List<PqStandardDevExcel> pqStandardDevExcels = BeanUtil.copyToList(pqStandardDevs, PqStandardDevExcel.class);
|
||||||
|
ExcelUtil.exportExcelPullDown(new ExportParams(), "标准设备导出数据.xlsx", 1, this.getStandardDevPullDownList(), PqStandardDevExcel.class, pqStandardDevExcels);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadTemplate() {
|
||||||
|
ExcelUtil.exportExcelPullDown(new ExportParams(), "标准设备导入模板.xlsx", 1, this.getStandardDevPullDownList(), PqStandardDevExcel.class, Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void importData(MultipartFile file, HttpServletResponse response) {
|
||||||
|
ImportParams params = new ImportParams();
|
||||||
|
params.setStartSheetIndex(0);
|
||||||
|
params.setSheetNum(1);
|
||||||
|
params.setNeedVerify(true);
|
||||||
|
params.setHeadRows(1);
|
||||||
|
|
||||||
|
List<PqStandardDevExcel> contrastDevExcelList = null;
|
||||||
|
try {
|
||||||
|
ExcelImportResult<PqStandardDevExcel> excelImportResult = ExcelImportUtil.importExcelMore(file.getInputStream(), PqStandardDevExcel.class, params);
|
||||||
|
if (excelImportResult.isVerifyFail()) {
|
||||||
|
// 此处前端要做特殊处理,具体可以参考技术监督的数据导入
|
||||||
|
Workbook failWorkbook = excelImportResult.getFailWorkbook();
|
||||||
|
PoiUtil.exportFileByWorkbook(failWorkbook, "非法被检设备数据.xlsx", response);
|
||||||
|
} else {
|
||||||
|
contrastDevExcelList = excelImportResult.getList();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new BusinessException(DetectionResponseEnum.IMPORT_DATA_FAIL);
|
||||||
|
}
|
||||||
|
this.importData(contrastDevExcelList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PqStandardDev> listByPlanId(String planId) {
|
||||||
|
return adPlanStandardDevMapper.listByPlanId(Collections.singletonList(planId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PreDetection> listStandardDevPreDetection(List<String> ids) {
|
||||||
|
if (CollectionUtil.isNotEmpty(ids)) {
|
||||||
|
return this.baseMapper.listStandardDevPreDetection(ids);
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入数据
|
||||||
|
*
|
||||||
|
* @param contrastDevExcelList
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private void importData(List<PqStandardDevExcel> contrastDevExcelList) {
|
||||||
|
if (CollectionUtil.isNotEmpty(contrastDevExcelList)) {
|
||||||
|
List<PqStandardDev> contrastDevs = BeanUtil.copyToList(contrastDevExcelList, PqStandardDev.class);
|
||||||
|
this.reverseVisualizeStandardDev(contrastDevs);
|
||||||
|
this.saveBatch(contrastDevs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取标准设备导出文件下拉列表
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private List<PullDown> getStandardDevPullDownList() {
|
||||||
|
List<PullDown> pullDownList = new ArrayList<>();
|
||||||
|
|
||||||
|
PullDown pullDown = new PullDown();
|
||||||
|
|
||||||
|
// 设备类型
|
||||||
|
pullDown.setFirstCol(1);
|
||||||
|
pullDown.setLastCol(1);
|
||||||
|
pullDown.setStrings(devTypeService.listAll().stream().map(devType -> devType.getName()).collect(Collectors.toList()));
|
||||||
|
pullDownList.add(pullDown);
|
||||||
|
|
||||||
|
|
||||||
|
List<DictData> dictDataList = null;
|
||||||
|
// 设备厂家
|
||||||
|
DictType dictType = dictTypeService.getByCode("Dev_Manufacturers");
|
||||||
|
if (ObjectUtil.isNotNull(dictType)) {
|
||||||
|
dictDataList = dictDataService.getDictDataByTypeId(dictType.getId());
|
||||||
|
|
||||||
|
pullDown = new PullDown();
|
||||||
|
pullDown.setFirstCol(2);
|
||||||
|
pullDown.setLastCol(2);
|
||||||
|
pullDown.setStrings(dictDataList.stream().map(DictData::getName).collect(Collectors.toList()));
|
||||||
|
pullDownList.add(pullDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通讯协议
|
||||||
|
dictType = dictTypeService.getByCode("Protocol");
|
||||||
|
if (ObjectUtil.isNotNull(dictType)) {
|
||||||
|
dictDataList = dictDataService.getDictDataByTypeId(dictType.getId());
|
||||||
|
|
||||||
|
pullDown = new PullDown();
|
||||||
|
pullDown.setFirstCol(4);
|
||||||
|
pullDown.setLastCol(4);
|
||||||
|
pullDown.setStrings(dictDataList.stream().map(DictData::getName).collect(Collectors.toList()));
|
||||||
|
pullDownList.add(pullDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否加密
|
||||||
|
pullDown = new PullDown();
|
||||||
|
pullDown.setFirstCol(7);
|
||||||
|
pullDown.setLastCol(7);
|
||||||
|
pullDown.setStrings(Arrays.asList("否", "是"));
|
||||||
|
pullDownList.add(pullDown);
|
||||||
|
|
||||||
|
return pullDownList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可视化标准设备
|
||||||
|
*
|
||||||
|
* @param pqStandardDevs
|
||||||
|
*/
|
||||||
|
private void visualizeStandardDev(List<PqStandardDev> pqStandardDevs) {
|
||||||
|
pqStandardDevs.forEach(pqStandardDev -> {
|
||||||
|
pqStandardDev.setDevType(devTypeService.getById(pqStandardDev.getDevType()).getName());
|
||||||
|
pqStandardDev.setManufacturer(dictDataService.getDictDataById(pqStandardDev.getManufacturer()).getName());
|
||||||
|
pqStandardDev.setProtocol(dictDataService.getDictDataById(pqStandardDev.getProtocol()).getName());
|
||||||
|
if (pqStandardDev.getEncryptionFlag() == 1) {
|
||||||
|
pqStandardDev.setSeries(EncryptionUtil.decoderString(1, pqStandardDev.getSeries()));
|
||||||
|
pqStandardDev.setDevKey(EncryptionUtil.decoderString(1, pqStandardDev.getDevKey()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逆向可视化标准设备
|
||||||
|
*
|
||||||
|
* @param pqStandardDevs
|
||||||
|
*/
|
||||||
|
private void reverseVisualizeStandardDev(List<PqStandardDev> pqStandardDevs) {
|
||||||
|
pqStandardDevs.forEach(pqStandardDev -> {
|
||||||
|
PqStandardDevParam pqStandardDevParam = BeanUtil.copyProperties(pqStandardDev, PqStandardDevParam.class);
|
||||||
|
this.checkRepeat(pqStandardDevParam, false);
|
||||||
|
this.checkEncryption(pqStandardDev);
|
||||||
|
pqStandardDev.setDevType(devTypeService.getByName(pqStandardDev.getDevType()).getId());
|
||||||
|
pqStandardDev.setManufacturer(dictDataService.getDictDataByName(pqStandardDev.getManufacturer()).getId());
|
||||||
|
pqStandardDev.setProtocol(dictDataService.getDictDataByName(pqStandardDev.getProtocol()).getId());
|
||||||
|
pqStandardDev.setState(DataStateEnum.ENABLE.getCode());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否重复
|
||||||
|
*
|
||||||
|
* @param pqStandardDevParam
|
||||||
|
* @param isExcludeSelf
|
||||||
|
*/
|
||||||
|
private void checkRepeat(PqStandardDevParam pqStandardDevParam, boolean isExcludeSelf) {
|
||||||
|
QueryWrapper<PqStandardDev> wrapper = new QueryWrapper<>();
|
||||||
|
wrapper.eq("pq_standard_dev.Name", pqStandardDevParam.getName())
|
||||||
|
.eq("pq_standard_dev.State", DataStateEnum.ENABLE.getCode());
|
||||||
|
if (isExcludeSelf) {
|
||||||
|
if (pqStandardDevParam instanceof PqStandardDevParam.UpdateParam) {
|
||||||
|
wrapper.ne("pq_standard_dev.Id", ((PqStandardDevParam.UpdateParam) pqStandardDevParam).getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int count = this.count(wrapper);
|
||||||
|
if (count > 0) {
|
||||||
|
throw new BusinessException(DetectionResponseEnum.PQ_STANDARD_DEV_REPEAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查加密情况
|
||||||
|
*
|
||||||
|
* @param pqStandardDev
|
||||||
|
*/
|
||||||
|
private void checkEncryption(PqStandardDev pqStandardDev) {
|
||||||
|
if (pqStandardDev.getEncryptionFlag() == 1) {
|
||||||
|
if (StrUtil.isNotBlank(pqStandardDev.getSeries()) && StrUtil.isNotBlank(pqStandardDev.getDevKey())) {
|
||||||
|
pqStandardDev.setSeries(EncryptionUtil.encodeString(1, pqStandardDev.getSeries()));
|
||||||
|
pqStandardDev.setDevKey(EncryptionUtil.encodeString(1, pqStandardDev.getDevKey()));
|
||||||
|
} else {
|
||||||
|
throw new BusinessException(DetectionResponseEnum.SERIES_AND_DEVKEY_NOT_BLANK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.listByIds(planIds);
|
||||||
|
// 区分主计划和子计划
|
||||||
|
List<AdPlan> mainPlanList = planList.stream().filter(plan -> plan.getFatherPlanId() == null).collect(Collectors.toList());
|
||||||
|
List<AdPlan> subPlanList = planList.stream().filter(plan -> plan.getFatherPlanId() != null).collect(Collectors.toList());
|
||||||
|
List<String> excludePlanIds = new ArrayList<>();
|
||||||
|
|
||||||
|
// 主计划直接排除
|
||||||
|
if (CollectionUtil.isNotEmpty(mainPlanList)) {
|
||||||
|
List<String> excludeMainPlanIds = mainPlanList.stream().filter(plan -> plan.getTestState() != 2).map(plan -> plan.getId()).collect(Collectors.toList());
|
||||||
|
excludePlanIds.addAll(excludeMainPlanIds);
|
||||||
|
}
|
||||||
|
// 子计划需要判断其主计划, 如果主计划未完成则排除
|
||||||
|
if (CollectionUtil.isNotEmpty(subPlanList)) {
|
||||||
|
List<String> fatherPlanIds = subPlanList.stream().map(plan -> plan.getFatherPlanId()).collect(Collectors.toList());
|
||||||
|
List<AdPlan> fatherPlanList = adPlanService.listByIds(fatherPlanIds);
|
||||||
|
List<String> excludeFatherPlanIds = fatherPlanList.stream()
|
||||||
|
.filter(plan -> plan.getTestState() != 2)
|
||||||
|
.map(plan -> plan.getId()).collect(Collectors.toList());
|
||||||
|
List<String> excludeSubPlanIds = subPlanList.stream()
|
||||||
|
.filter(plan -> excludeFatherPlanIds.contains(plan.getFatherPlanId()))
|
||||||
|
.map(plan -> plan.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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.lambdaQuery()
|
||||||
|
.eq(PqStandardDev::getState, DataStateEnum.ENABLE.getCode())
|
||||||
|
.notIn(CollectionUtil.isNotEmpty(excludeStandardDevIds), PqStandardDev::getId, excludeStandardDevIds)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user