Compare commits
1553 Commits
EICS-V1.9.
...
Test_IRC_N
| Author | SHA1 | Date |
|---|---|---|
|
|
91e7d04e4d | |
|
|
711fa5a2b8 | |
|
|
9f7bc4be75 | |
|
|
df7022b2e3 | |
|
|
6ee51a7f43 | |
|
|
aed8a77e13 | |
|
|
bcfa22a818 | |
|
|
0840d57689 | |
|
|
deb7e77263 | |
|
|
aba477a49c | |
|
|
a9e5a7754e | |
|
|
a2d4d100d3 | |
|
|
7f4f5e9d08 | |
|
|
55a2301bb5 | |
|
|
14ac852eee | |
|
|
d2e53c7b8c | |
|
|
4a335d2a28 | |
|
|
999fde1f5c | |
|
|
024e62103a | |
|
|
73889c51ae | |
|
|
7a0b29bda6 | |
|
|
a592a39fae | |
|
|
aa96ef66c6 | |
|
|
2b24ce723e | |
|
|
867ee32f91 | |
|
|
662001accd | |
|
|
334b2fe440 | |
|
|
2f728629f3 | |
|
|
eb8628da5b | |
|
|
1427940224 | |
|
|
06f10e38f5 | |
|
|
5092f5204f | |
|
|
13f4420e42 | |
|
|
e0e6e60ae0 | |
|
|
9cac49d539 | |
|
|
97d9aefbeb | |
|
|
340119f697 | |
|
|
a075eb34cd | |
|
|
53913d3777 | |
|
|
c1d79a7e9f | |
|
|
3ee6f0ceaa | |
|
|
1821d82ded | |
|
|
476ca95aa3 | |
|
|
d8b799f057 | |
|
|
4c4807c22e | |
|
|
8a9afc2d9a | |
|
|
6fcb670a89 | |
|
|
d224b519f7 | |
|
|
b382595767 | |
|
|
64ff6f73d4 | |
|
|
e1632b58ac | |
|
|
7e39692cc4 | |
|
|
8de44fb7b3 | |
|
|
647aee010a | |
|
|
296459d38a | |
|
|
7941328a29 | |
|
|
38d9a21a5d | |
|
|
7d88f49e68 | |
|
|
3c46215a3c | |
|
|
137ed07b2b | |
|
|
6f05850fbc | |
|
|
80447f5c7a | |
|
|
971d94495a | |
|
|
77790a1cb2 | |
|
|
ecfadcb5a3 | |
|
|
d9e3b642a5 | |
|
|
e56e269f2e | |
|
|
98f7ab8c02 | |
|
|
5d33bd03a7 | |
|
|
55882aa0ba | |
|
|
43d1a94f22 | |
|
|
53b2b9156f | |
|
|
fafc075735 | |
|
|
09ee238e50 | |
|
|
7be3813527 | |
|
|
e6491a6203 | |
|
|
4955a24d6f | |
|
|
425f53b74c | |
|
|
2ed9595186 | |
|
|
4d1e3beda6 | |
|
|
8b0e1b8988 | |
|
|
c77682a9ad | |
|
|
8e0ff1f2b2 | |
|
|
b5e027a553 | |
|
|
ef712949ad | |
|
|
b429b2cf8c | |
|
|
9fb95c3030 | |
|
|
6883d47508 | |
|
|
dae44745ca | |
|
|
24b7d2e1b9 | |
|
|
21ac6850d2 | |
|
|
8b69a07b93 | |
|
|
f92bf347e3 | |
|
|
78669051de | |
|
|
ea90b2b38b | |
|
|
bd61a0f2e9 | |
|
|
888ad8897e | |
|
|
eb18021988 | |
|
|
7b243a9615 | |
|
|
5716c4169a | |
|
|
abb9f47646 | |
|
|
9a5221ef7d | |
|
|
d9eaa06de3 | |
|
|
4f91874b82 | |
|
|
139438133d | |
|
|
6705cf0fc0 | |
|
|
00255e902c | |
|
|
71e867156f | |
|
|
658f437f73 | |
|
|
95bf613263 | |
|
|
03d61347ca | |
|
|
050a0875fe | |
|
|
d9c46acb03 | |
|
|
f19fc0e92f | |
|
|
3bc4b86749 | |
|
|
43455da7bc | |
|
|
0030eac22e | |
|
|
671c071a45 | |
|
|
3419015542 | |
|
|
3eea6cf8b7 | |
|
|
2c407c6e4e | |
|
|
ab90591e7c | |
|
|
33a2b7f373 | |
|
|
9158f28998 | |
|
|
680a6cb437 | |
|
|
21c19f4b6d | |
|
|
8c03167f7e | |
|
|
b629b2eec4 | |
|
|
01af8f72d3 | |
|
|
4d10db054c | |
|
|
a019a542b0 | |
|
|
b42325337e | |
|
|
a3152bb6e1 | |
|
|
4ec0441d28 | |
|
|
9adc8a93b1 | |
|
|
3f3d1a562b | |
|
|
1b6ba91240 | |
|
|
c406976a6d | |
|
|
d213fa7a99 | |
|
|
60707150a6 | |
|
|
008906d245 | |
|
|
762b8b7425 | |
|
|
24228428fa | |
|
|
9148651556 | |
|
|
1c9c3eb893 | |
|
|
17e0517e5b | |
|
|
7662880352 | |
|
|
c3c433244e | |
|
|
df65d592ad | |
|
|
7873523c1b | |
|
|
8b5800b32e | |
|
|
2e46791466 | |
|
|
fc0ef2e89f | |
|
|
1de687cbf3 | |
|
|
3a017dbfa3 | |
|
|
1f828b403b | |
|
|
f75fafaa42 | |
|
|
7c561a4656 | |
|
|
033b849bdc | |
|
|
8ae0b0d621 | |
|
|
b6bad061b7 | |
|
|
1dd8515ac2 | |
|
|
9f3bb29004 | |
|
|
52dd676cf9 | |
|
|
342b567e2c | |
|
|
35ee76be0b | |
|
|
b4d40a4770 | |
|
|
84bfcb4d3e | |
|
|
f030ed9b66 | |
|
|
c6920a1995 | |
|
|
2d681ce6bc | |
|
|
3981f2cd50 | |
|
|
2b961c7985 | |
|
|
fb2fdd2c27 | |
|
|
f6bcefd1e8 | |
|
|
1d7b28e7f9 | |
|
|
3ea2bdfc92 | |
|
|
4aa0161c41 | |
|
|
ff4ab39025 | |
|
|
bb35ae1b9a | |
|
|
0e0ce59d74 | |
|
|
82d3157e2a | |
|
|
12cb53b167 | |
|
|
5fc53a1999 | |
|
|
b712abd1d5 | |
|
|
a1c13b5850 | |
|
|
a0fe7be308 | |
|
|
bbfa966b57 | |
|
|
6d11fc68b4 | |
|
|
eef764dd90 | |
|
|
8423b00bec | |
|
|
dbaac006c7 | |
|
|
510c73dd24 | |
|
|
41486798d2 | |
|
|
f40faa5519 | |
|
|
eecfe6dcae | |
|
|
8a004ef35f | |
|
|
369805e6c6 | |
|
|
93b93f6a3a | |
|
|
9fbee7e0c9 | |
|
|
46704d5299 | |
|
|
4225014cdd | |
|
|
ae36d4680a | |
|
|
d48f736392 | |
|
|
b0d8a49929 | |
|
|
e45791c676 | |
|
|
ca425c602b | |
|
|
44e7ffaeeb | |
|
|
cf42b2e57f | |
|
|
840de08e69 | |
|
|
929903aa1b | |
|
|
5eafcc9c8c | |
|
|
a1657f5827 | |
|
|
dff2e712ad | |
|
|
a5f3430965 | |
|
|
5059d66aa1 | |
|
|
668af5ee0b | |
|
|
a70caefe01 | |
|
|
64bd6a5604 | |
|
|
b68deafa82 | |
|
|
d29c150081 | |
|
|
4d3ecd05dc | |
|
|
143d7f0c4b | |
|
|
4d2f04b30e | |
|
|
955ca4a1a6 | |
|
|
6e92895043 | |
|
|
6102309828 | |
|
|
8f34644d45 | |
|
|
f347b34f59 | |
|
|
dc7e71dc74 | |
|
|
749bf95761 | |
|
|
a67472c9b4 | |
|
|
32c8d70179 | |
|
|
510dfcac30 | |
|
|
9b59a55423 | |
|
|
0bd269f93e | |
|
|
e55a13cfb1 | |
|
|
1617de2fc1 | |
|
|
40c3ea65ff | |
|
|
541e000183 | |
|
|
0e93894ed8 | |
|
|
7f1621915f | |
|
|
70e4829f32 | |
|
|
c77b3d74c8 | |
|
|
1f4ea09bc6 | |
|
|
c45020f24f | |
|
|
ca679057c4 | |
|
|
456793293b | |
|
|
75d3908911 | |
|
|
a7fbd00530 | |
|
|
785d5ec869 | |
|
|
c5feb98ed8 | |
|
|
121b1d4eb4 | |
|
|
8e2b99333c | |
|
|
5bd055c77d | |
|
|
5ea9d63cb9 | |
|
|
0134d6c723 | |
|
|
a9c80a670b | |
|
|
998b92aa79 | |
|
|
60ce8c45b2 | |
|
|
a66d25f564 | |
|
|
91b66660a6 | |
|
|
1031222e47 | |
|
|
2005eacaa9 | |
|
|
b581afebe6 | |
|
|
1532ac4a7e | |
|
|
231b3f71c5 | |
|
|
308f5140a1 | |
|
|
ef673fc44b | |
|
|
f347b0cace | |
|
|
102cf5f6dc | |
|
|
d9ca71009f | |
|
|
114ee50273 | |
|
|
54bb66f738 | |
|
|
19f52bc133 | |
|
|
2439dec9fb | |
|
|
fb0fd7d91f | |
|
|
3765b390a6 | |
|
|
f6be3186e7 | |
|
|
7399568e36 | |
|
|
37d798627a | |
|
|
fe2a375b4e | |
|
|
70443b7b8d | |
|
|
7a37c01b7f | |
|
|
7215c75f5a | |
|
|
a34c47dc3c | |
|
|
6fdad9a482 | |
|
|
08e62e4500 | |
|
|
3cfb4556f3 | |
|
|
b8e3e83b80 | |
|
|
7e9bf65f9f | |
|
|
5197e49c1f | |
|
|
7880836f39 | |
|
|
487eb9dca8 | |
|
|
473cee0e30 | |
|
|
817cdcc8f7 | |
|
|
bf03430958 | |
|
|
58321c4c82 | |
|
|
8cabc8c96a | |
|
|
7f050a1dd6 | |
|
|
e5e2a0394b | |
|
|
a56ab0680b | |
|
|
ee13d0ae2e | |
|
|
ee2c66c8fb | |
|
|
f1fb2068de | |
|
|
95e0239e42 | |
|
|
0b28ef5461 | |
|
|
68fe2b75dc | |
|
|
ecfeee1693 | |
|
|
9cda4360ca | |
|
|
18b9c08101 | |
|
|
d4e89aec20 | |
|
|
01a3a9057b | |
|
|
2107a42e37 | |
|
|
d96e746db7 | |
|
|
be034d4d8b | |
|
|
ca44b4786a | |
|
|
74056167d6 | |
|
|
e6aec6b559 | |
|
|
9a488610da | |
|
|
f82420b7ef | |
|
|
fbeaec2820 | |
|
|
b4907bfe9b | |
|
|
6e75e45942 | |
|
|
3292b9f978 | |
|
|
1959045ab2 | |
|
|
6a99e3104a | |
|
|
ab91d360b1 | |
|
|
50a4a82819 | |
|
|
498c02431b | |
|
|
fd54d64ae3 | |
|
|
ac80e3a455 | |
|
|
c6a55949b3 | |
|
|
18547cdec1 | |
|
|
d29707c8ee | |
|
|
c7e29116dd | |
|
|
a85cc1236c | |
|
|
7b4c603fa6 | |
|
|
67c93e654e | |
|
|
9b0c874314 | |
|
|
d172e48c79 | |
|
|
2a2419af90 | |
|
|
7e39094b13 | |
|
|
8936e9db99 | |
|
|
bd92d48232 | |
|
|
57f7b29cc7 | |
|
|
f55f498626 | |
|
|
955fe25d30 | |
|
|
86c24e6002 | |
|
|
b835a028ad | |
|
|
c8d5b94154 | |
|
|
d03d294e97 | |
|
|
28014139f5 | |
|
|
b42a86922e | |
|
|
09bf2b4d91 | |
|
|
3685686447 | |
|
|
b6d2128034 | |
|
|
9545e699c2 | |
|
|
87955d2ca0 | |
|
|
0a8573091e | |
|
|
a713ffdc61 | |
|
|
406235cc03 | |
|
|
1095e07f87 | |
|
|
14b8cbac23 | |
|
|
92063da2bf | |
|
|
9c6b835800 | |
|
|
f71b71bb54 | |
|
|
6d39e72506 | |
|
|
a90edaff51 | |
|
|
bb8fb0a968 | |
|
|
348ca93086 | |
|
|
e3b2046cb8 | |
|
|
99350c3a17 | |
|
|
1523450b88 | |
|
|
fb20b1f1b2 | |
|
|
5c90b66fc9 | |
|
|
bc1a6562d5 | |
|
|
be1fbf2059 | |
|
|
7a3bbbeb19 | |
|
|
d263ecbe2a | |
|
|
447e916564 | |
|
|
48c9721ab4 | |
|
|
e641e78fb3 | |
|
|
d37c1cb872 | |
|
|
da2afd0d5a | |
|
|
09305928ed | |
|
|
e16fd2f4de | |
|
|
b2389403d9 | |
|
|
29c137a2c5 | |
|
|
2e1a68fa14 | |
|
|
7ec76f6ce3 | |
|
|
87e70447f3 | |
|
|
f72e3f4300 | |
|
|
7154196667 | |
|
|
0081cf16d0 | |
|
|
2eec5f02da | |
|
|
4b011ee727 | |
|
|
b115dd5253 | |
|
|
84349c19e7 | |
|
|
84d08c0378 | |
|
|
197aa9bf0a | |
|
|
f83819d0c9 | |
|
|
43c0885e0a | |
|
|
1cff4f7603 | |
|
|
71f978609b | |
|
|
774e7ccc25 | |
|
|
c89b65b550 | |
|
|
a8800b01f3 | |
|
|
fcc2a68618 | |
|
|
a353c90bf7 | |
|
|
8e730fc342 | |
|
|
53149bf8a0 | |
|
|
b2d681da09 | |
|
|
49a36b8214 | |
|
|
270941431d | |
|
|
2e3c209de2 | |
|
|
0be5763ce3 | |
|
|
7f1232e2ff | |
|
|
9669032e6c | |
|
|
fd5711a31d | |
|
|
25c5b32fa7 | |
|
|
5f75413c9c | |
|
|
9fa73705ba | |
|
|
0f27e246a6 | |
|
|
0d26daa3b0 | |
|
|
8186ec1b02 | |
|
|
0f0967f893 | |
|
|
56165f5037 | |
|
|
0b707117c6 | |
|
|
5186a530f8 | |
|
|
b3ed2dd497 | |
|
|
5cfa209cc2 | |
|
|
7f6b6010f2 | |
|
|
ca5accfaa0 | |
|
|
12376a154e | |
|
|
955ecb116b | |
|
|
79f18826c5 | |
|
|
d756845953 | |
|
|
c01d3cbd14 | |
|
|
d20b399b9c | |
|
|
21f549a2e4 | |
|
|
b1e3290090 | |
|
|
0f859c0251 | |
|
|
875a3668c1 | |
|
|
eafceeb5d1 | |
|
|
44156a2e34 | |
|
|
2ebf61220c | |
|
|
d10152b720 | |
|
|
695de70894 | |
|
|
5956937476 | |
|
|
2c3f35c26b | |
|
|
a13917bded | |
|
|
48d48b343e | |
|
|
6987dd075f | |
|
|
8bc3d9b121 | |
|
|
90671b2c34 | |
|
|
492312f595 | |
|
|
3215d68504 | |
|
|
f5a229c1d5 | |
|
|
a267bc13b4 | |
|
|
de976453f5 | |
|
|
7ee821aebd | |
|
|
4fc60d03d4 | |
|
|
02e9d1b7cd | |
|
|
1050e988c3 | |
|
|
5eb152a5af | |
|
|
c8f9a0dcea | |
|
|
75abc7834b | |
|
|
12c242d340 | |
|
|
d95158913e | |
|
|
677a7f7bd7 | |
|
|
fcdee39356 | |
|
|
7e54b4fdd6 | |
|
|
f69ce8b757 | |
|
|
24d1277b1c | |
|
|
71bedb5b5c | |
|
|
5abc573e81 | |
|
|
0c9f1f0191 | |
|
|
d4d70f4cff | |
|
|
87035563fb | |
|
|
447d0149fd | |
|
|
7f4c1b57b2 | |
|
|
688c1d195c | |
|
|
0e12f2cdac | |
|
|
f2f8d00e31 | |
|
|
935dafb917 | |
|
|
b92c5c9ceb | |
|
|
89ba8406a1 | |
|
|
e4b06786de | |
|
|
6e586fb6d3 | |
|
|
cfd05cff36 | |
|
|
2eb19a28b3 | |
|
|
21f70f4d79 | |
|
|
db8d13725e | |
|
|
e60f9eb689 | |
|
|
6461c64923 | |
|
|
0f3050b28d | |
|
|
6d298e775f | |
|
|
83f9a66f7a | |
|
|
ed40a896f2 | |
|
|
1c58ba7e28 | |
|
|
49e372d11d | |
|
|
1a6410e881 | |
|
|
cf8b9706fc | |
|
|
1fc92a12e4 | |
|
|
ce3c4a893f | |
|
|
f3613febb8 | |
|
|
9a1a4e3451 | |
|
|
752acd8d4d | |
|
|
73a1236a5f | |
|
|
11c03576ea | |
|
|
836c7f7185 | |
|
|
6355231e8b | |
|
|
1b05055e3f | |
|
|
a11edc3053 | |
|
|
33e84a05ef | |
|
|
24a082c0f0 | |
|
|
e9bd228e09 | |
|
|
b0f4b6a3d5 | |
|
|
a001f5476f | |
|
|
a51ced9c4e | |
|
|
1bcf8647ef | |
|
|
832930a5d9 | |
|
|
d4164f13dd | |
|
|
d2b6a1dbe9 | |
|
|
d1bf244135 | |
|
|
394e090029 | |
|
|
7fc643bae1 | |
|
|
4f1072e545 | |
|
|
59d5addf72 | |
|
|
034a0e8bbb | |
|
|
f32a66a89b | |
|
|
84994e0d3c | |
|
|
d193834e05 | |
|
|
fa9c7c8cf8 | |
|
|
5ef075e4fe | |
|
|
c18c0aadd0 | |
|
|
eccb127492 | |
|
|
e979649ef4 | |
|
|
62789f4cd8 | |
|
|
8e2477c19d | |
|
|
6c103b577c | |
|
|
9082caa57c | |
|
|
3b4cb52fab | |
|
|
088143fb70 | |
|
|
9e63e742bf | |
|
|
8431ece7f9 | |
|
|
ef03bac3f2 | |
|
|
f8f0b5661a | |
|
|
704fd261fc | |
|
|
329cc3b6bf | |
|
|
766ce3123a | |
|
|
5bebaa247a | |
|
|
71a4b241fd | |
|
|
228e50c3df | |
|
|
fee78546c0 | |
|
|
06118b08c5 | |
|
|
47b8d5186e | |
|
|
19f700acc6 | |
|
|
4b4347c839 | |
|
|
854df31461 | |
|
|
58ce173543 | |
|
|
5c668ee733 | |
|
|
4b0dd31fd1 | |
|
|
23ec2a6275 | |
|
|
043037328e | |
|
|
0b1a4bb53e | |
|
|
3eb3478353 | |
|
|
cb25fa309f | |
|
|
1f8f3242b5 | |
|
|
a52a06cf89 | |
|
|
0475b9891c | |
|
|
0a927d6087 | |
|
|
f06bc9654c | |
|
|
eacdcb1eb1 | |
|
|
11d57fb0b6 | |
|
|
0d91ceafff | |
|
|
c81cdf30a0 | |
|
|
facb4cdc47 | |
|
|
3f31c8ad14 | |
|
|
76a2ef3cbd | |
|
|
3c1ecf8e43 | |
|
|
7a9e2f95ca | |
|
|
582ea4c5ab | |
|
|
668f93d0ac | |
|
|
f2d6d62083 | |
|
|
2e8ffaecff | |
|
|
77a250fc71 | |
|
|
1ce5938e78 | |
|
|
e221af244d | |
|
|
e4883f116f | |
|
|
57b7e3f65c | |
|
|
26b17c12bb | |
|
|
f10b62c548 | |
|
|
8eae9dde16 | |
|
|
469e7cb49b | |
|
|
3f6e81bc3e | |
|
|
1dafe81770 | |
|
|
549691c8ac | |
|
|
364ce9aefb | |
|
|
27d15599c1 | |
|
|
356384142a | |
|
|
d0f0b5046c | |
|
|
beb6f2fca8 | |
|
|
d933df437c | |
|
|
fbf7fc6a70 | |
|
|
c84753f99d | |
|
|
c1995f1547 | |
|
|
ffe6a0a4a9 | |
|
|
2541d08fba | |
|
|
76fe4ae8a8 | |
|
|
85ff14f160 | |
|
|
a8224aa018 | |
|
|
18d2ea7d31 | |
|
|
5ea31ee1b0 | |
|
|
e3e83612fc | |
|
|
36750cc81c | |
|
|
d93cfb6a4d | |
|
|
fed62ce5e3 | |
|
|
c66c7eaef7 | |
|
|
3d671c79ea | |
|
|
dae5ee098a | |
|
|
f2836a8928 | |
|
|
a86448a5c1 | |
|
|
0b8e6fc2e8 | |
|
|
bae6f099fe | |
|
|
1d74e33b8b | |
|
|
caca3e77a5 | |
|
|
e9b79cc1f1 | |
|
|
435479cd9d | |
|
|
f191da2df3 | |
|
|
72e8106516 | |
|
|
85d58d3ca2 | |
|
|
1c05010552 | |
|
|
1aad2c6a3f | |
|
|
08ff24b14a | |
|
|
530d11c769 | |
|
|
24135801ac | |
|
|
d225e26d95 | |
|
|
a0b14d5957 | |
|
|
f776d2e663 | |
|
|
5099ad654e | |
|
|
b68e91b71e | |
|
|
fa209e54b6 | |
|
|
316caa8482 | |
|
|
05bd1bd9b8 | |
|
|
3e4026e899 | |
|
|
cf52923060 | |
|
|
67a5f1c32c | |
|
|
405ab20ef5 | |
|
|
47e8183d4f | |
|
|
d76f600e03 | |
|
|
c7b10af268 | |
|
|
ff08efd978 | |
|
|
af1b0cbbb9 | |
|
|
21dd3f455c | |
|
|
3eaca22700 | |
|
|
3f6ea7cf05 | |
|
|
81a4b55e13 | |
|
|
235c87ced0 | |
|
|
fb30808e01 | |
|
|
24b11f1b38 | |
|
|
a17235d9dd | |
|
|
e11ab15e6c | |
|
|
132a0a8eba | |
|
|
ec2a717b9b | |
|
|
8bbd93409e | |
|
|
94ba76b3f3 | |
|
|
bf3ebebb0f | |
|
|
723531d0a4 | |
|
|
903344160e | |
|
|
5b0dfa8d7a | |
|
|
8272dc71d5 | |
|
|
e01945f273 | |
|
|
1cf961c75d | |
|
|
4214fcc870 | |
|
|
56429d0b37 | |
|
|
e39d2e8ff8 | |
|
|
16099cc6d8 | |
|
|
899ef3034f | |
|
|
1671141b12 | |
|
|
887b45f3d2 | |
|
|
31aca49f1a | |
|
|
9239cc490b | |
|
|
9a5258bf83 | |
|
|
5e31d0d853 | |
|
|
3b24e85d5f | |
|
|
574629970c | |
|
|
c556061a79 | |
|
|
e6d2f13b7e | |
|
|
24ec6b21d5 | |
|
|
fd52011be9 | |
|
|
ef351674b7 | |
|
|
fa979b57cc | |
|
|
36594c349e | |
|
|
053edf9e4d | |
|
|
addd8fb321 | |
|
|
4417767e35 | |
|
|
02beaa24fc | |
|
|
48f924e64f | |
|
|
85fa51150d | |
|
|
c8a70fd385 | |
|
|
09db5e13b8 | |
|
|
710d4f54b0 | |
|
|
d66e92b971 | |
|
|
7436b4ca5d | |
|
|
d43d38719e | |
|
|
416b9ea4d5 | |
|
|
395e7b4388 | |
|
|
3fbccad84c | |
|
|
b2e494843d | |
|
|
4624b0f12e | |
|
|
cb68dc8fb5 | |
|
|
e8b2196bc4 | |
|
|
b6a795b8b0 | |
|
|
1e96e671a9 | |
|
|
164b074d1b | |
|
|
bce2ed0dfa | |
|
|
88cb7b8a1a | |
|
|
50b47e0752 | |
|
|
7189ea563c | |
|
|
4d185f6f99 | |
|
|
e0592989b0 | |
|
|
8bbe424456 | |
|
|
dd35e37e65 | |
|
|
c49bd189b6 | |
|
|
6880b48f73 | |
|
|
474b9ca80e | |
|
|
6ef41cae1d | |
|
|
8831834be6 | |
|
|
d567b9d252 | |
|
|
be4826a6ba | |
|
|
5ac9a37de2 | |
|
|
546d8e2cc3 | |
|
|
8d81115525 | |
|
|
4c60a32c7e | |
|
|
e1755eb11a | |
|
|
9e1b61fbf5 | |
|
|
f3915dc5b7 | |
|
|
4a6cac2327 | |
|
|
5cc8a246da | |
|
|
f8f66b1110 | |
|
|
40f8887ef9 | |
|
|
cf59838779 | |
|
|
e628ed91e7 | |
|
|
c8fef30b15 | |
|
|
c860b5c365 | |
|
|
49b2ba2484 | |
|
|
d35bdb6899 | |
|
|
b7fe79208e | |
|
|
e1e7abb65a | |
|
|
c71f7e38cc | |
|
|
981585498d | |
|
|
7c3b71de93 | |
|
|
34f6292fa7 | |
|
|
6c82cbe11d | |
|
|
587c12f331 | |
|
|
d4dc00804b | |
|
|
df54375e6d | |
|
|
e5597d25b9 | |
|
|
b6149c4d32 | |
|
|
373a9a2bb1 | |
|
|
256a5a6ea6 | |
|
|
8baf6ddf53 | |
|
|
066f66b969 | |
|
|
57ab098e12 | |
|
|
5aae3b7fc4 | |
|
|
e0fef9f4ca | |
|
|
e898f17873 | |
|
|
8456108db8 | |
|
|
3603e7c3ab | |
|
|
43339ba6e0 | |
|
|
8715032780 | |
|
|
efb9532fbc | |
|
|
7264c4a417 | |
|
|
50d3974cdf | |
|
|
879a85a26f | |
|
|
8f5d391c0d | |
|
|
f8ba3019e6 | |
|
|
61b67b935a | |
|
|
7cf1798dd7 | |
|
|
33c5efc8d7 | |
|
|
f8e501fb0d | |
|
|
bdb48f93bc | |
|
|
32cd6e0c74 | |
|
|
397a36214c | |
|
|
5d49a0c9b9 | |
|
|
485e1359a9 | |
|
|
0333002ffa | |
|
|
14b1a62a1f | |
|
|
f52e35379f | |
|
|
e32b7c2ca4 | |
|
|
e902fe88d0 | |
|
|
6d9cfe5e45 | |
|
|
bc5b93b924 | |
|
|
e94f971495 | |
|
|
12a2683bc3 | |
|
|
e3e1d6bbb7 | |
|
|
46c824a84a | |
|
|
a8ad59eb91 | |
|
|
bdaa997416 | |
|
|
b770f8765d | |
|
|
8d8865cef7 | |
|
|
1e72d83284 | |
|
|
2260a5621e | |
|
|
b7efd0c97f | |
|
|
f13c2bf7cc | |
|
|
49a7d5fc29 | |
|
|
efb9201598 | |
|
|
e17e3a4b53 | |
|
|
aa2bd91426 | |
|
|
fb66e2c738 | |
|
|
c44c37b76b | |
|
|
6b068e3919 | |
|
|
92c0612a90 | |
|
|
bb9d7da55f | |
|
|
c0f7a921d6 | |
|
|
0a6f2e2011 | |
|
|
b30d11b5dd | |
|
|
df1e87897f | |
|
|
e86a5e005c | |
|
|
3859dc0d79 | |
|
|
61d1641716 | |
|
|
06af37f74c | |
|
|
d9b46437a4 | |
|
|
4ee760ed53 | |
|
|
b030959b64 | |
|
|
814f27244a | |
|
|
e53531636d | |
|
|
6d100555d8 | |
|
|
fea55de838 | |
|
|
d9326a1e8e | |
|
|
b44c92f291 | |
|
|
a51130b4ea | |
|
|
424441a3fc | |
|
|
dce62d076b | |
|
|
04422cd795 | |
|
|
1548498d3d | |
|
|
40fd8b0d5a | |
|
|
b143ffbcfc | |
|
|
1d8be8c04b | |
|
|
139e1f8186 | |
|
|
81cb43d3a3 | |
|
|
c2db7a2c61 | |
|
|
8cd19b929c | |
|
|
c62e7b13a9 | |
|
|
fc81738176 | |
|
|
b1e1840c87 | |
|
|
b31d7ee762 | |
|
|
fb0c3879cf | |
|
|
5ffe788a3b | |
|
|
056573874a | |
|
|
c959a47fa6 | |
|
|
41fe678708 | |
|
|
e7bbf09a50 | |
|
|
3bd16d7fce | |
|
|
db1d1b0f14 | |
|
|
ef980fdc68 | |
|
|
4de186a36b | |
|
|
0dd73991f0 | |
|
|
27e8e0e79c | |
|
|
fa53d6e88e | |
|
|
f3fc3fa4bf | |
|
|
476f692355 | |
|
|
41b2a638fe | |
|
|
c3631eee51 | |
|
|
3ed74a578e | |
|
|
918728a42e | |
|
|
a8c0bef99e | |
|
|
b3468a6d1b | |
|
|
dba8d3c964 | |
|
|
8f5b024d3b | |
|
|
ea836d9349 | |
|
|
c32878911d | |
|
|
0c4bd680dd | |
|
|
90cd6ad0eb | |
|
|
013561541c | |
|
|
f1f584c337 | |
|
|
c443969f18 | |
|
|
01621511f1 | |
|
|
0740b38b10 | |
|
|
ebd70e2202 | |
|
|
80c026d5b2 | |
|
|
8e2fbf06c5 | |
|
|
daee20e309 | |
|
|
3ba7481df8 | |
|
|
1e299e3f9e | |
|
|
05cc46a6d6 | |
|
|
f1b943b6b0 | |
|
|
9f00c1c49b | |
|
|
b1fd623065 | |
|
|
7683866718 | |
|
|
d58395c25e | |
|
|
15dd21451e | |
|
|
483c7c68e8 | |
|
|
738ad29c0b | |
|
|
f87b57ecc5 | |
|
|
fccd704659 | |
|
|
e5b41735ec | |
|
|
07b496eb67 | |
|
|
762e46480c | |
|
|
a7ad22ed28 | |
|
|
8610a627ea | |
|
|
302658d467 | |
|
|
97c882b0b2 | |
|
|
d711bb4b7f | |
|
|
1a4ca6e36b | |
|
|
aaac85b7b2 | |
|
|
cb3e4bc671 | |
|
|
9f187e4b36 | |
|
|
663ec11c8d | |
|
|
122447a621 | |
|
|
31bd3c646b | |
|
|
f53fc409cb | |
|
|
7f4357de68 | |
|
|
23fa689498 | |
|
|
e564debbab | |
|
|
04aa2c79f6 | |
|
|
68d66523e8 | |
|
|
549f394d43 | |
|
|
5a83bf9221 | |
|
|
65bffa26b7 | |
|
|
39a6f6a9dc | |
|
|
fc02428435 | |
|
|
4262189508 | |
|
|
6dd6a5f696 | |
|
|
e03f2e35b5 | |
|
|
ea4a3db145 | |
|
|
f4ac12d4f8 | |
|
|
a2afe4f6e4 | |
|
|
ef31afa1d8 | |
|
|
d9fda60b99 | |
|
|
6508698bda | |
|
|
734675a1a5 | |
|
|
d2697d5133 | |
|
|
1c7f4eac90 | |
|
|
6dcaa953d4 | |
|
|
1f67d24abc | |
|
|
c7a310c1ad | |
|
|
578aaa467c | |
|
|
82a7c4de29 | |
|
|
fe4df21a68 | |
|
|
9cb6fb2a92 | |
|
|
37cfe07ea6 | |
|
|
52ffdac948 | |
|
|
83a15a0b47 | |
|
|
1952a108a9 | |
|
|
ccff36507f | |
|
|
285fd0e1bb | |
|
|
918aafa7e5 | |
|
|
60accb2f87 | |
|
|
018c9afee0 | |
|
|
1b9351b23f | |
|
|
86ea3373fc | |
|
|
dd3b43da7b | |
|
|
3151e42e78 | |
|
|
5d8f94ef65 | |
|
|
26acc94172 | |
|
|
5c663ec3e9 | |
|
|
5c0917f14e | |
|
|
74230da400 | |
|
|
df104ec527 | |
|
|
5cdef2051a | |
|
|
470d05131e | |
|
|
5e046b847d | |
|
|
8b6fcf9ae1 | |
|
|
4dfa1e9812 | |
|
|
a05cb71e0d | |
|
|
137b18446c | |
|
|
e44478dafe | |
|
|
0a5a36adb3 | |
|
|
91e4553795 | |
|
|
f504cfee9e | |
|
|
2e7e3acf68 | |
|
|
c00b98daf8 | |
|
|
235f420e97 | |
|
|
da15b352a1 | |
|
|
9857b1ae92 | |
|
|
9e83f8df36 | |
|
|
ae431df9b5 | |
|
|
e5592c5bfa | |
|
|
1b2bca9f1e | |
|
|
4235e6025a | |
|
|
209ce439c2 | |
|
|
eabf8f4532 | |
|
|
07ddb7b234 | |
|
|
1f157bd1a2 | |
|
|
0c28f85f49 | |
|
|
44536b803f | |
|
|
750c31a159 | |
|
|
8325ccad72 | |
|
|
c27c4ce617 | |
|
|
b9ecabf915 | |
|
|
4f13a98eff | |
|
|
2a4e4e66fa | |
|
|
b72cc5cbc0 | |
|
|
04f91560b8 | |
|
|
87af2edf17 | |
|
|
856977612d | |
|
|
d1ac17cee1 | |
|
|
9748e70cdf | |
|
|
5ba0a9bbd0 | |
|
|
d8ea1e8f94 | |
|
|
806d663cfb | |
|
|
7ab1728bc7 | |
|
|
32e64eaeff | |
|
|
7b6efd52bb | |
|
|
7467f2ae11 | |
|
|
c498ffb21b | |
|
|
1dadf7b8b6 | |
|
|
793cf1b570 | |
|
|
5a8f564b74 | |
|
|
e24c9e6ac2 | |
|
|
814f6fd83c | |
|
|
d34be9b07d | |
|
|
e205ea0018 | |
|
|
ba9d3a30b9 | |
|
|
10cf908775 | |
|
|
ee9ad472f0 | |
|
|
1cdfc5f2b5 | |
|
|
9e9f07e4fd | |
|
|
a0294e6bd6 | |
|
|
55fdf431e0 | |
|
|
9f1110f91a | |
|
|
d76b24bc25 | |
|
|
0787875b3b | |
|
|
b33aca3230 | |
|
|
0b291e8b21 | |
|
|
0cf7e5fddd | |
|
|
95f708da58 | |
|
|
ce47776f19 | |
|
|
7fff48b07a | |
|
|
4688cb388e | |
|
|
21e1a7ab01 | |
|
|
5952af85f1 | |
|
|
5e193db33b | |
|
|
30bcfcc675 | |
|
|
514764ee6e | |
|
|
685269eebf | |
|
|
276ad83080 | |
|
|
7a318eae45 | |
|
|
0769f8fe05 | |
|
|
03ac25835e | |
|
|
29c3888108 | |
|
|
91951be107 | |
|
|
935e46a8f1 | |
|
|
1345808019 | |
|
|
400d0d3336 | |
|
|
ca0fd77176 | |
|
|
f7cf6d9669 | |
|
|
7f19944005 | |
|
|
e8aec5f04c | |
|
|
0afbd24923 | |
|
|
932772f8c8 | |
|
|
b7a803678c | |
|
|
e99360bf5b | |
|
|
5064630d1f | |
|
|
4034da47f2 | |
|
|
381b1965cf | |
|
|
d5e747becd | |
|
|
758273d362 | |
|
|
1560a0278e | |
|
|
64c275a91c | |
|
|
05189b4e5a | |
|
|
80aaac997d | |
|
|
c560921092 | |
|
|
81cd9aa6b7 | |
|
|
4bfc6dd295 | |
|
|
a8464364c1 | |
|
|
e333368528 | |
|
|
58c1c25fc2 | |
|
|
3045b0ec48 | |
|
|
2d50037c03 | |
|
|
6a1cd07951 | |
|
|
c7103654e3 | |
|
|
4e03071c4c | |
|
|
bf9e8a6758 | |
|
|
00c6705161 | |
|
|
209d932757 | |
|
|
e954ea8538 | |
|
|
f79a8b4cfb | |
|
|
8fc01e2509 | |
|
|
232e607b8c | |
|
|
bdeb7c464e | |
|
|
3668079479 | |
|
|
c115e9453b | |
|
|
f157a0f473 | |
|
|
c2252b4e9f | |
|
|
b0554119f1 | |
|
|
37387692dd | |
|
|
970c41a064 | |
|
|
67a3fe0f1b | |
|
|
15108156c2 | |
|
|
1a2c51da12 | |
|
|
3d10482da8 | |
|
|
3abc8787c6 | |
|
|
df346d31e6 | |
|
|
a8babb53f2 | |
|
|
3c1cc92e1b | |
|
|
99ee8c4757 | |
|
|
fc81d6da22 | |
|
|
47edcbd761 | |
|
|
a499971e91 | |
|
|
afd34c7c9b | |
|
|
529c646277 | |
|
|
b8f03153ca | |
|
|
a14df73879 | |
|
|
cb9141c1ce | |
|
|
ff2461dafc | |
|
|
779af013e6 | |
|
|
75fb357cc5 | |
|
|
89eb1032cf | |
|
|
eca9c36110 | |
|
|
2430651ed4 | |
|
|
87d658031b | |
|
|
34b90538ab | |
|
|
d2571d5f79 | |
|
|
1688d7c934 | |
|
|
9ab5dc7d02 | |
|
|
97cdec61be | |
|
|
c0d6eb3203 | |
|
|
d125ff6324 | |
|
|
1b830b386e | |
|
|
ee83909123 | |
|
|
844e27e94e | |
|
|
6fded9f028 | |
|
|
b5103f1940 | |
|
|
8bf54dfc1b | |
|
|
1069292c54 | |
|
|
88c6f574ec | |
|
|
f5f6d3b6ac | |
|
|
38f937ea9e | |
|
|
2913eb67f4 | |
|
|
a0ceec28e7 | |
|
|
b77a50caf6 | |
|
|
e17fc6bce9 | |
|
|
0970e2807b | |
|
|
a64d26513a | |
|
|
73e80f2571 | |
|
|
770f361d04 | |
|
|
7476472071 | |
|
|
1fff2f7256 | |
|
|
1e4044488d | |
|
|
1e1822151b | |
|
|
c37b6c4c84 | |
|
|
8f65b9e60e | |
|
|
a8a9a09f88 | |
|
|
7526ca2905 | |
|
|
fd83c3e7a7 | |
|
|
a3c575857a | |
|
|
c292f6ce44 | |
|
|
84a7d75174 | |
|
|
048afdcc7b | |
|
|
06e319cf09 | |
|
|
cee0730e03 | |
|
|
adaa672185 | |
|
|
b6b5d3e4c9 | |
|
|
6ac057a0c7 | |
|
|
6607a0ff8f | |
|
|
0374551655 | |
|
|
be9f0075e3 | |
|
|
2cc004746e | |
|
|
b1a7ac1d28 | |
|
|
5a31e16c65 | |
|
|
0b56836097 | |
|
|
a4a11aaaec | |
|
|
ae1b2523b1 | |
|
|
87f9d08c74 | |
|
|
81abd9622f | |
|
|
faf87f5b86 | |
|
|
66dc231850 | |
|
|
34ff47bc1c | |
|
|
842b7117a2 | |
|
|
6543aba53f | |
|
|
06cee8926e | |
|
|
a35fe11f5b | |
|
|
cc13544968 | |
|
|
19ee511d8d | |
|
|
0b47d6ca09 | |
|
|
00193bb4bc | |
|
|
4379fcbdb3 | |
|
|
8464625bbf | |
|
|
e88f949cb7 | |
|
|
4fdb0737fc | |
|
|
04be8cf642 | |
|
|
089ecbf144 | |
|
|
b95e97a5df | |
|
|
e84710c4aa | |
|
|
f549d1fca9 | |
|
|
2c72dc000f | |
|
|
2ceddc1340 | |
|
|
f118c48d0e | |
|
|
a3a1236d5b | |
|
|
a7848727ee | |
|
|
5787c36687 | |
|
|
4e66ea43d7 | |
|
|
68d69181a9 | |
|
|
b879b3f2c7 | |
|
|
4fd77ce70d | |
|
|
8213c6d10f | |
|
|
b79b4567b4 | |
|
|
8c5b759a92 | |
|
|
ab1e33a799 | |
|
|
824b65d5ee | |
|
|
c68004b804 | |
|
|
d5cd8dc338 | |
|
|
1fef5509d1 | |
|
|
06d12ce709 | |
|
|
ad7ac80444 | |
|
|
29d0ebdb7c | |
|
|
9bcaa07c1a | |
|
|
bf5f56f5f2 | |
|
|
c1c2b59641 | |
|
|
33820c2acb | |
|
|
9814f63246 | |
|
|
5e6c018199 | |
|
|
b24751287c | |
|
|
759cc6c1be | |
|
|
1a929ce2ca | |
|
|
1e4571a814 | |
|
|
848670d128 | |
|
|
6d9b5fdb2d | |
|
|
ab00d64725 | |
|
|
5e36054ee9 | |
|
|
2b11042f35 | |
|
|
e9e5075c1c | |
|
|
9149dd6cf6 | |
|
|
24bfb4920c | |
|
|
553ad4760c | |
|
|
5acd56689b | |
|
|
5f0643d4d4 | |
|
|
17e4ed38be | |
|
|
c49f16b055 | |
|
|
aa42d64cac | |
|
|
8f0ef86ab9 | |
|
|
c17b2f6bac | |
|
|
be498f2329 | |
|
|
fea3d8d415 | |
|
|
bffc8626c7 | |
|
|
1efe1228ac | |
|
|
f900b811c7 | |
|
|
a5f7e03f6c | |
|
|
7aba4adc14 | |
|
|
12c8c641ea | |
|
|
977adf3153 | |
|
|
409b76df26 | |
|
|
2d9ebe7094 | |
|
|
71e797cf7d | |
|
|
f781027388 | |
|
|
56e316e6a1 | |
|
|
edef3e1990 | |
|
|
d6d0965467 | |
|
|
62f888710c | |
|
|
58e8c2392f | |
|
|
0e1310324d | |
|
|
efcda61104 | |
|
|
97133a5554 | |
|
|
577ed42f97 | |
|
|
8f704c4617 | |
|
|
a03d7f55c7 | |
|
|
2bbc101f95 | |
|
|
2b11e9165f | |
|
|
b25dec68cb | |
|
|
2f57238f66 | |
|
|
8239061a84 | |
|
|
a43ebc016a | |
|
|
c036aab9c4 | |
|
|
dd6419dc44 | |
|
|
54a9cfbb8e | |
|
|
2eb85b6b71 | |
|
|
712f1ceb61 | |
|
|
adf873639c | |
|
|
4e9460e7da | |
|
|
5481eac988 | |
|
|
b7c9abca02 | |
|
|
447d3fc36f | |
|
|
71702b2abb | |
|
|
2ffb24d630 | |
|
|
81ecd9ff7f | |
|
|
ebe012ca67 | |
|
|
805176c3ea | |
|
|
f99cab6983 | |
|
|
c94d800646 | |
|
|
0cbc345f49 | |
|
|
d34f3f1576 | |
|
|
73f5e67e3c | |
|
|
5cf8c7a898 | |
|
|
11c9cebf47 | |
|
|
bd9362fb25 | |
|
|
3aa588f125 | |
|
|
6d33a2c1b0 | |
|
|
9e4cf1d1db | |
|
|
ae38c84ef7 | |
|
|
6306c6752a | |
|
|
5490630922 | |
|
|
d1ea886e76 | |
|
|
7dc1205bb6 | |
|
|
15b094a422 | |
|
|
78c46572a2 | |
|
|
abeda7820d | |
|
|
2dac67618c | |
|
|
bb9b1283bf | |
|
|
b9458ecb38 | |
|
|
b205757f8f | |
|
|
0b937663ff | |
|
|
7a75d107dc | |
|
|
0f682dd184 | |
|
|
c2360dd519 | |
|
|
f9c2ac030a | |
|
|
7ce2386aea | |
|
|
67b226ee44 | |
|
|
70c7f8a740 | |
|
|
e409aa3c05 | |
|
|
a3b5129ee2 | |
|
|
57cd0281fa | |
|
|
ba9d3abc52 | |
|
|
b9bc1f6da0 | |
|
|
08f1998e61 | |
|
|
bc19ead2f5 | |
|
|
f9327c56db | |
|
|
17f3d821ea | |
|
|
02f3e97654 | |
|
|
94bba7837c | |
|
|
626fcb9cf3 | |
|
|
bf92165432 | |
|
|
92e99edbbd | |
|
|
94507c7887 | |
|
|
0cce75befa | |
|
|
96469c28d3 | |
|
|
a12db2637e | |
|
|
0a406da3a4 | |
|
|
6271dd4144 | |
|
|
4182809657 | |
|
|
ab6ec064fa | |
|
|
d921a48a09 | |
|
|
0f03dbc75b | |
|
|
e5fa3a09b3 | |
|
|
4cdfb60fdf | |
|
|
200bdaccc4 | |
|
|
17a35e0fdb | |
|
|
28f2bf1629 | |
|
|
bec04ab795 | |
|
|
54cefab633 | |
|
|
2918c0e93f | |
|
|
92c495ed07 | |
|
|
d9d807e3d1 | |
|
|
959b49a2cc | |
|
|
17f131cf63 | |
|
|
4ef67beafc | |
|
|
826a04ef31 | |
|
|
104b1208ed | |
|
|
8b3040c24d | |
|
|
153dbd2016 | |
|
|
91d5cbce16 | |
|
|
7218427ec2 | |
|
|
5d91ae8157 | |
|
|
0f61d746e5 | |
|
|
e931e740e6 | |
|
|
38917e05ab | |
|
|
dd33f7e086 | |
|
|
43d4d679b5 | |
|
|
e02fa28cd0 | |
|
|
055e085d2a | |
|
|
caf1fb6b32 | |
|
|
6b049e68cd | |
|
|
2a4360cc64 | |
|
|
8efc254f3c | |
|
|
f89b3daf50 | |
|
|
29f6196698 | |
|
|
09ff2468e9 | |
|
|
1867277e1b | |
|
|
053a09a5b2 | |
|
|
fae86f0836 | |
|
|
2e539dfa71 | |
|
|
a9efd85aec | |
|
|
09e70b9f21 | |
|
|
2d184e0f08 | |
|
|
43d6c7d8db | |
|
|
19487acc64 | |
|
|
336e7768b8 | |
|
|
9b49dc41c8 | |
|
|
74f896cf00 | |
|
|
10b0bde1d0 | |
|
|
411b5d5f8f | |
|
|
b6da27ac07 | |
|
|
a6b81b038f | |
|
|
e2c80babfa | |
|
|
01b9f28e7b | |
|
|
2c9fad60fb | |
|
|
398baea876 | |
|
|
aec94d0a46 | |
|
|
4b69e45d44 | |
|
|
12f2c2becb | |
|
|
699f79278b | |
|
|
88e07d28fd | |
|
|
226ba06bf3 | |
|
|
c1667fc48b | |
|
|
f900e675da | |
|
|
ee51b9eade | |
|
|
bf62ee92dd | |
|
|
78532803bc | |
|
|
86ea3ebc32 | |
|
|
01aba3f3e0 | |
|
|
6dc1ef5728 | |
|
|
02e5d770db | |
|
|
f8c3704c87 | |
|
|
4c21459e00 | |
|
|
cdbbe460ab | |
|
|
45da1645e0 | |
|
|
9a1f4697c2 | |
|
|
217b8713ff | |
|
|
0998f047e4 | |
|
|
47755bc2aa | |
|
|
72efd36649 | |
|
|
553a8d392f | |
|
|
f050416265 | |
|
|
a7d1a92a01 | |
|
|
bf9ce9b213 | |
|
|
a9c7c79fb5 | |
|
|
dc3a830909 | |
|
|
171bfde031 | |
|
|
f471ce1535 | |
|
|
56170b73ed | |
|
|
2a9c1ec45c | |
|
|
3ccb3ff4de | |
|
|
6c078806cd | |
|
|
c2f768bef8 | |
|
|
1b7bb5a4fe | |
|
|
dfe6d13182 | |
|
|
4c04718db4 | |
|
|
64d803cbc0 | |
|
|
54ccae5762 | |
|
|
8d6bf0c7b1 | |
|
|
8496e6bcd7 | |
|
|
7770fc264b | |
|
|
51f21805be | |
|
|
434e49ea85 | |
|
|
468f7c7070 | |
|
|
4577db7582 | |
|
|
cc17816dae | |
|
|
172cecebe4 | |
|
|
7913acdf70 | |
|
|
e9f57b5001 | |
|
|
b8c0bd7082 | |
|
|
6200ec1ba7 | |
|
|
73912fd875 | |
|
|
812035686e | |
|
|
1f8caa4616 | |
|
|
4a1e0a87ba | |
|
|
337bda45fd | |
|
|
c2412bfb4b | |
|
|
cff6b87daa | |
|
|
2f93a490f0 | |
|
|
1856ef9483 | |
|
|
b45602556c | |
|
|
b5326d4928 | |
|
|
8bbc8c449f | |
|
|
dcd6375d7d | |
|
|
8b53517f2c | |
|
|
1c3ddcf1cd | |
|
|
fc4ef6b939 | |
|
|
6d009c4f86 | |
|
|
f995187925 | |
|
|
67a6a5f861 | |
|
|
6bfa4da72d | |
|
|
9797c94aa8 | |
|
|
5dd2dfd17b | |
|
|
5791aca9fd | |
|
|
f61ce70b82 | |
|
|
c8091d3215 | |
|
|
7153d810aa | |
|
|
68053d24b6 | |
|
|
a49b4c8ef7 | |
|
|
c6c036c152 | |
|
|
db0d087396 | |
|
|
263f43b1a9 | |
|
|
21626cda9d | |
|
|
77d920a2ef | |
|
|
bbf7af8405 | |
|
|
d0bbc58b20 | |
|
|
fed2cbe043 | |
|
|
4d4bb08423 | |
|
|
dd09c78cd2 | |
|
|
771091a557 | |
|
|
8ce1bdf21c | |
|
|
679cc26225 | |
|
|
ff5be827d0 | |
|
|
4555997957 | |
|
|
3b43bee0f1 | |
|
|
3677a30bab | |
|
|
2e8b2a24ae | |
|
|
26500aa1cf | |
|
|
adfd6b50c1 | |
|
|
b58bdc184e | |
|
|
580eea7a61 | |
|
|
da27b65e56 | |
|
|
bb493fe827 | |
|
|
655fe18138 | |
|
|
9d70d61013 | |
|
|
68452beba0 | |
|
|
409e9e9bc0 | |
|
|
f241f2cd2b | |
|
|
2ea90a66ae | |
|
|
c2c8f65aaa | |
|
|
a578be8336 | |
|
|
45b469682a | |
|
|
1e9f4d890f | |
|
|
44f74fd9e6 | |
|
|
f1c8415899 | |
|
|
823ada5673 | |
|
|
55f146a44a | |
|
|
5fe972bd99 | |
|
|
0578263e10 | |
|
|
9316c0814f | |
|
|
8bdd9e29ab | |
|
|
ae14a8a530 | |
|
|
2a170cc97e | |
|
|
c109e14a93 | |
|
|
240d406a1a | |
|
|
b6cf988058 | |
|
|
c16153fa19 | |
|
|
a0797a99ff | |
|
|
376cfa3167 | |
|
|
14690dc3cc | |
|
|
6dd0831a66 | |
|
|
a150156810 | |
|
|
d61fba9d95 | |
|
|
7cb8d04ca9 | |
|
|
a2fc2c5075 | |
|
|
1ed8788c83 | |
|
|
14e95fd303 | |
|
|
3dca5aa06f | |
|
|
faea3a4f86 | |
|
|
a5dcad1767 | |
|
|
6754121b17 | |
|
|
131425774a | |
|
|
585e4cdc1b | |
|
|
12ddeba2a3 | |
|
|
ab0c256e0b | |
|
|
8a24b33757 | |
|
|
8f6e2e342a | |
|
|
4dfeb653b1 | |
|
|
670a754eb2 | |
|
|
3f46103dc3 | |
|
|
6c2901daca | |
|
|
a73e5d4133 | |
|
|
b067cb66f3 | |
|
|
f1185758d6 | |
|
|
0d4a0adf00 | |
|
|
20f6f95b89 | |
|
|
7e7c186784 | |
|
|
70e0a1325a | |
|
|
91471dee13 |
|
|
@ -0,0 +1,37 @@
|
|||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
||||
namespace IRaCIS.Core.SCP.Filter
|
||||
{
|
||||
|
||||
|
||||
public class ModelActionFilter : ActionFilterAttribute, IActionFilter
|
||||
{
|
||||
public IStringLocalizer _localizer;
|
||||
public ModelActionFilter(IStringLocalizer localizer)
|
||||
{
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
if (!context.ModelState.IsValid)
|
||||
{
|
||||
|
||||
var validationErrors = context.ModelState
|
||||
.Keys
|
||||
.SelectMany(k => context.ModelState[k]!.Errors)
|
||||
.Select(e => e.ErrorMessage)
|
||||
.ToArray();
|
||||
|
||||
//---提供给接口的参数无效。
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ModelAction_InvalidAPIParameter"] + JsonConvert.SerializeObject( validationErrors)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Filter
|
||||
{
|
||||
public class ProjectExceptionFilter : Attribute, IExceptionFilter
|
||||
{
|
||||
private readonly ILogger<ProjectExceptionFilter> _logger;
|
||||
|
||||
public IStringLocalizer _localizer;
|
||||
|
||||
public ProjectExceptionFilter(IStringLocalizer localizer, ILogger<ProjectExceptionFilter> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_localizer = localizer;
|
||||
}
|
||||
public void OnException(ExceptionContext context)
|
||||
{
|
||||
//context.ExceptionHandled;//记录当前这个异常是否已经被处理过了
|
||||
|
||||
if (!context.ExceptionHandled)
|
||||
{
|
||||
if (context.Exception.GetType().Name == "DbUpdateConcurrencyException")
|
||||
{
|
||||
//---并发更新,当前不允许该操作
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ProjectException_ConcurrentUpdateNotAllowed"] + context.Exception.Message));
|
||||
}
|
||||
|
||||
if (context.Exception.GetType() == typeof(BusinessValidationFailedException))
|
||||
{
|
||||
var error = context.Exception as BusinessValidationFailedException;
|
||||
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(context.Exception.Message, error!.Code));
|
||||
}
|
||||
else if(context.Exception.GetType() == typeof(QueryBusinessObjectNotExistException))
|
||||
{
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk( context.Exception.Message, ApiResponseCodeEnum.DataNotExist));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (context.Exception.InnerException is null ? (context.Exception.Message /*+ context.Exception.StackTrace*/)
|
||||
: (context.Exception.InnerException?.Message /*+ context.Exception.InnerException?.StackTrace*/)), ApiResponseCodeEnum.ProgramException));
|
||||
}
|
||||
|
||||
|
||||
_logger.LogError(context.Exception.InnerException is null ? (context.Exception.Message + context.Exception.StackTrace) : (context.Exception.InnerException?.Message + context.Exception.InnerException?.StackTrace));
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//继续
|
||||
}
|
||||
context.ExceptionHandled = true;//标记当前异常已经被处理过了
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
|
||||
namespace IRaCIS.Core.Application.Service.BusinessFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 统一返回前端数据包装,之前在控制器包装,现在修改为动态Api 在ResultFilter这里包装,减少重复冗余代码
|
||||
/// by zhouhang 2021.09.12 周末
|
||||
/// </summary>
|
||||
public class UnifiedApiResultFilter : Attribute, IAsyncResultFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步版本
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="next"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
|
||||
{
|
||||
|
||||
if (context.Result is ObjectResult objectResult)
|
||||
{
|
||||
var statusCode = objectResult.StatusCode ?? context.HttpContext.Response.StatusCode;
|
||||
|
||||
//是200 并且没有包装 那么包装结果
|
||||
if (statusCode == 200 && !(objectResult.Value is IResponseOutput))
|
||||
{
|
||||
//if (objectResult.Value == null)
|
||||
//{
|
||||
// var apiResponse = ResponseOutput.DBNotExist();
|
||||
|
||||
// objectResult.Value = apiResponse;
|
||||
// objectResult.DeclaredType = apiResponse.GetType();
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
|
||||
var type = objectResult.Value?.GetType();
|
||||
|
||||
|
||||
if ( type!=null&& type.IsGenericType&&(type.GetGenericTypeDefinition()==typeof(ValueTuple<,>)|| type.GetGenericTypeDefinition()==typeof(Tuple<,>)))
|
||||
{
|
||||
|
||||
//报错
|
||||
//var tuple = (object, object))objectResult.Value;
|
||||
|
||||
//var (val1, val2) = ((dynamic, dynamic))objectResult.Value;
|
||||
//var apiResponse = ResponseOutput.Ok(val1, val2);
|
||||
|
||||
//OK
|
||||
var tuple = (dynamic)objectResult.Value;
|
||||
var apiResponse = ResponseOutput.Ok(tuple.Item1, tuple.Item2);
|
||||
|
||||
|
||||
objectResult.Value = apiResponse;
|
||||
objectResult.DeclaredType = apiResponse.GetType();
|
||||
}
|
||||
else
|
||||
{
|
||||
var apiResponse = ResponseOutput.Ok(objectResult.Value);
|
||||
|
||||
objectResult.Value = apiResponse;
|
||||
objectResult.DeclaredType = apiResponse.GetType();
|
||||
}
|
||||
|
||||
|
||||
//}
|
||||
|
||||
}
|
||||
//如果不是200 是IResponseOutput 不处理
|
||||
else if (statusCode != 200 && (objectResult.Value is IResponseOutput))
|
||||
{
|
||||
}
|
||||
|
||||
else if(statusCode != 200&&!(objectResult.Value is IResponseOutput))
|
||||
{
|
||||
//---程序错误,请联系开发人员。
|
||||
var apiResponse = ResponseOutput.NotOk(I18n.T("UnifiedAPI_ProgramError"));
|
||||
|
||||
objectResult.Value = apiResponse;
|
||||
objectResult.DeclaredType = apiResponse.GetType();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
await next.Invoke();
|
||||
|
||||
}
|
||||
|
||||
public static bool IsTupleType(Type type, bool checkBaseTypes = false)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
if (type == typeof(Tuple))
|
||||
return true;
|
||||
|
||||
while (type != null)
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var genType = type.GetGenericTypeDefinition();
|
||||
if (genType == typeof(Tuple<>)
|
||||
|| genType == typeof(Tuple<,>)
|
||||
|| genType == typeof(Tuple<,>))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!checkBaseTypes)
|
||||
break;
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
using Autofac;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Panda.DynamicWebApi;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Application.Service;
|
||||
using AutoMapper;
|
||||
using IRaCIS.Core.SCP.Service;
|
||||
|
||||
namespace IRaCIS.Core.SCP
|
||||
{
|
||||
// ReSharper disable once IdentifierTypo
|
||||
public class AutofacModuleSetup : Autofac.Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder containerBuilder)
|
||||
{
|
||||
|
||||
#region byzhouhang 20210917 此处注册泛型仓储 可以减少Domain层 和Infra.EFcore 两层 空的仓储接口定义和 仓储文件定义
|
||||
|
||||
containerBuilder.RegisterGeneric(typeof(Repository<>))
|
||||
.As(typeof(IRepository<>)).InstancePerLifetimeScope();//注册泛型仓储
|
||||
|
||||
containerBuilder.RegisterType<Repository>().As<IRepository>().InstancePerLifetimeScope();
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region 指定控制器也由autofac 来进行实例获取 https://www.cnblogs.com/xwhqwer/p/15320838.html
|
||||
|
||||
//获取所有控制器类型并使用属性注入
|
||||
containerBuilder.RegisterAssemblyTypes(typeof(BaseService).Assembly)
|
||||
.Where(type => typeof(IDynamicWebApi).IsAssignableFrom(type))
|
||||
.PropertiesAutowired();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
Assembly application = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + typeof(BaseService).Assembly.GetName().Name+".dll");
|
||||
containerBuilder.RegisterAssemblyTypes(application).Where(t => t.FullName.Contains("Service"))
|
||||
.PropertiesAutowired().AsImplementedInterfaces();
|
||||
|
||||
|
||||
//containerBuilder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
|
||||
//containerBuilder.RegisterType<UserInfo>().As<IUserInfo>().InstancePerLifetimeScope();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
using EntityFramework.Exceptions.SqlServer;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using Medallion.Threading;
|
||||
using Medallion.Threading.SqlServer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace IRaCIS.Core.SCP
|
||||
{
|
||||
public static class EFSetup
|
||||
{
|
||||
public static void AddEFSetup( this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddScoped<IUserInfo, UserInfo>();
|
||||
services.AddScoped<ISaveChangesInterceptor, AuditEntityInterceptor>();
|
||||
|
||||
|
||||
//这个注入没有成功--注入是没问题的,构造函数也只是支持参数就好,错在注入的地方不能写DbContext
|
||||
//Web程序中通过重用池中DbContext实例可提高高并发场景下的吞吐量, 这在概念上类似于ADO.NET Provider原生的连接池操作方式,具有节省DbContext实例化成本的优点
|
||||
services.AddDbContext<IRaCISDBContext>((sp, options) =>
|
||||
{
|
||||
// 在控制台
|
||||
//public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
|
||||
var logFactory = LoggerFactory.Create(builder => { builder.AddDebug(); });
|
||||
|
||||
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value,
|
||||
contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
|
||||
|
||||
options.UseLoggerFactory(logFactory);
|
||||
|
||||
options.UseExceptionProcessor();
|
||||
|
||||
options.EnableSensitiveDataLogging();
|
||||
|
||||
options.AddInterceptors(new QueryWithNoLockDbCommandInterceptor());
|
||||
options.AddInterceptors(sp.GetServices<ISaveChangesInterceptor>());
|
||||
|
||||
options.UseProjectables();
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
//// Register an additional context factory as a Scoped service, which gets a pooled context from the Singleton factory we registered above,
|
||||
//services.AddScoped<IRaCISDBScopedFactory>();
|
||||
|
||||
//// Finally, arrange for a context to get injected from our Scoped factory:
|
||||
//services.AddScoped(sp => sp.GetRequiredService<IRaCISDBScopedFactory>().CreateDbContext());
|
||||
|
||||
//注意区分 easy caching 也有 IDistributedLockProvider
|
||||
services.AddSingleton<IDistributedLockProvider>(sp =>
|
||||
{
|
||||
//var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
|
||||
|
||||
return new SqlDistributedSynchronizationProvider(configuration.GetSection("ConnectionStrings:RemoteNew").Value);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System;
|
||||
|
||||
namespace IRaCIS.Core.SCP
|
||||
{
|
||||
public static class NewtonsoftJsonSetup
|
||||
{
|
||||
public static void AddNewtonsoftJsonSetup(this IMvcBuilder builder, IServiceCollection services)
|
||||
{
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddScoped<IOSSService,OSSService>();
|
||||
|
||||
builder.AddNewtonsoftJson(options =>
|
||||
{
|
||||
//options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
|
||||
// 忽略循环引用
|
||||
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||
//options.SerializerSettings.TypeNameHandling = TypeNameHandling.All;
|
||||
|
||||
//处理返回给前端 可空类型 给出默认值 比如in? 为null 设置 默认值0
|
||||
options.SerializerSettings.ContractResolver = new NullToEmptyStringResolver(); //new DefaultContractResolver();// new NullToEmptyStringResolver();
|
||||
// 设置时间格式
|
||||
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind;
|
||||
|
||||
//options.SerializerSettings.Converters.Add(new JSONCustomDateConverter()) ;
|
||||
|
||||
//options.SerializerSettings.Converters.Add(services.BuildServiceProvider().GetService<JSONTimeZoneConverter>());
|
||||
|
||||
|
||||
|
||||
})
|
||||
.AddControllersAsServices()//动态webApi属性注入需要
|
||||
.ConfigureApiBehaviorOptions(o =>
|
||||
{
|
||||
o.SuppressModelStateInvalidFilter = true; //自己写验证
|
||||
|
||||
});
|
||||
|
||||
|
||||
Newtonsoft.Json.JsonSerializerSettings setting = new Newtonsoft.Json.JsonSerializerSettings();
|
||||
JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
|
||||
{
|
||||
//日期类型默认格式化处理
|
||||
setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";
|
||||
setting.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||
|
||||
return setting;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace IRaCIS.Core.SCP
|
||||
{
|
||||
public class NullToEmptyStringResolver : DefaultContractResolver
|
||||
{
|
||||
|
||||
|
||||
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
|
||||
{
|
||||
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
|
||||
|
||||
var list= type.GetProperties()
|
||||
.Select(p =>
|
||||
{
|
||||
var jp = base.CreateProperty(p, memberSerialization);
|
||||
jp.ValueProvider = new NullToEmptyStringValueProvider(p);
|
||||
return jp;
|
||||
}).ToList();
|
||||
|
||||
var uu = list.Select(t => t.PropertyName).ToList();
|
||||
|
||||
//获取复杂对象属性
|
||||
properties = properties.TakeWhile(t => !uu.Contains(t.PropertyName)).ToList();
|
||||
|
||||
list.AddRange(properties);
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace IRaCIS.Core.SCP
|
||||
{
|
||||
|
||||
public class NullToEmptyStringValueProvider : IValueProvider
|
||||
{
|
||||
PropertyInfo _MemberInfo;
|
||||
public NullToEmptyStringValueProvider(PropertyInfo memberInfo)
|
||||
{
|
||||
_MemberInfo = memberInfo;
|
||||
}
|
||||
public object GetValue(object target)
|
||||
{
|
||||
object result = _MemberInfo.GetValue(target);
|
||||
if (_MemberInfo.PropertyType == typeof(string) && result == null) result = "";
|
||||
else if (_MemberInfo.PropertyType == typeof(String[]) && result == null) result = new string[] { };
|
||||
//else if (_MemberInfo.PropertyType == typeof(Nullable<Int32>) && result == null) result = 0;
|
||||
else if (_MemberInfo.PropertyType == typeof(Nullable<Decimal>) && result == null) result = 0.00M;
|
||||
|
||||
return result;
|
||||
}
|
||||
public void SetValue(object target, object value)
|
||||
{
|
||||
|
||||
if(_MemberInfo.PropertyType == typeof(string))
|
||||
{
|
||||
//去掉前后空格
|
||||
_MemberInfo.SetValue(target, value==null?string.Empty: value.ToString()==string.Empty? value:value.ToString().Trim());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_MemberInfo.SetValue(target, value);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.5" />
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.416.8" />
|
||||
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.401.81" />
|
||||
<PackageReference Include="DistributedLock.Core" Version="1.0.8" />
|
||||
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" />
|
||||
<PackageReference Include="fo-dicom" Version="5.2.1" />
|
||||
<PackageReference Include="fo-dicom.Codecs" Version="5.16.1" />
|
||||
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
<PackageReference Include="Minio" Version="6.0.4" />
|
||||
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
|
||||
<TreatAsUsed>true</TreatAsUsed>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
|
||||
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IRaCIS.Core.Infra.EFCore\IRaCIS.Core.Infra.EFCore.csproj" />
|
||||
<ProjectReference Include="..\IRaCIS.Core.Infrastructure\IRaCIS.Core.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Helper\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
|
||||
using Autofac;
|
||||
using Autofac.Extensions.DependencyInjection;
|
||||
using AutoMapper.EquivalencyExpression;
|
||||
using FellowOakDicom;
|
||||
using FellowOakDicom.Imaging;
|
||||
using FellowOakDicom.Imaging.NativeCodec;
|
||||
using FellowOakDicom.Network;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.SCP;
|
||||
using IRaCIS.Core.SCP.Filter;
|
||||
using IRaCIS.Core.SCP.Service;
|
||||
using MassTransit;
|
||||
using MassTransit.NewIdProviders;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Panda.DynamicWebApi;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
||||
//以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数)
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
var enviromentName = config["ASPNETCORE_ENVIRONMENT"];
|
||||
|
||||
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
|
||||
{
|
||||
EnvironmentName = enviromentName
|
||||
});
|
||||
|
||||
|
||||
|
||||
#region 主机配置
|
||||
|
||||
NewId.SetProcessIdProvider(new CurrentProcessIdProvider());
|
||||
|
||||
builder.Configuration.AddJsonFile("appsettings.json", false, true)
|
||||
.AddJsonFile($"appsettings.{enviromentName}.json", false, true);
|
||||
builder.Host
|
||||
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
|
||||
.ConfigureContainer<ContainerBuilder>(containerBuilder =>
|
||||
{
|
||||
containerBuilder.RegisterModule<AutofacModuleSetup>();
|
||||
})
|
||||
.UseSerilog();
|
||||
#endregion
|
||||
|
||||
#region 配置服务
|
||||
var _configuration = builder.Configuration;
|
||||
|
||||
//健康检查
|
||||
builder.Services.AddHealthChecks();
|
||||
|
||||
//本地化
|
||||
builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resources");
|
||||
|
||||
|
||||
// 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim()
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
options.Filters.Add<ModelActionFilter>();
|
||||
options.Filters.Add<ProjectExceptionFilter>();
|
||||
options.Filters.Add<UnitOfWorkFilter>();
|
||||
|
||||
|
||||
})
|
||||
.AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理
|
||||
|
||||
|
||||
builder.Services.AddOptions().Configure<AliyunOSSOptions>(_configuration.GetSection("AliyunOSS"));
|
||||
builder.Services.AddOptions().Configure<ObjectStoreServiceOptions>(_configuration.GetSection("ObjectStoreService"));
|
||||
builder.Services.AddOptions().Configure<DicomSCPServiceOption>(_configuration.GetSection("DicomSCPServiceConfig"));
|
||||
|
||||
|
||||
//动态WebApi + UnifiedApiResultFilter 省掉控制器代码
|
||||
//动态webApi 目前存在的唯一小坑是生成api上服务上的动态代理AOP失效 间接掉用不影响
|
||||
builder.Services
|
||||
.AddDynamicWebApi(dynamicWebApiOption =>
|
||||
{
|
||||
//默认是 api
|
||||
dynamicWebApiOption.DefaultApiPrefix = "";
|
||||
//首字母小写
|
||||
dynamicWebApiOption.GetRestFulActionName = (actionName) => char.ToLower(actionName[0]) + actionName.Substring(1);
|
||||
//删除 Service后缀
|
||||
dynamicWebApiOption.RemoveControllerPostfixes.Add("Service");
|
||||
|
||||
});
|
||||
|
||||
//AutoMapper
|
||||
builder.Services.AddAutoMapper(automapper =>
|
||||
{
|
||||
|
||||
automapper.AddCollectionMappers();
|
||||
|
||||
|
||||
}, typeof(BaseService).Assembly);
|
||||
|
||||
//EF ORM QueryWithNoLock
|
||||
builder.Services.AddEFSetup(_configuration);
|
||||
|
||||
builder.Services.AddMediator(cfg =>
|
||||
{
|
||||
|
||||
});
|
||||
|
||||
|
||||
//转发头设置 获取真实IP
|
||||
builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
options.ForwardedHeaders =
|
||||
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
||||
});
|
||||
|
||||
|
||||
builder.Services.AddFellowOakDicom().AddTranscoderManager<NativeTranscoderManager>()
|
||||
//.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
|
||||
.AddImageManager<ImageSharpImageManager>();
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
//if (app.Environment.IsDevelopment())
|
||||
//{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
//}
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
#region 日志
|
||||
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
//.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.WriteTo.Console()
|
||||
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 运行环境 部署平台
|
||||
|
||||
Log.Logger.Warning($"当前环境:{enviromentName}");
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
Log.Logger.Warning($"当前部署平台环境:windows");
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
Log.Logger.Warning($"当前部署平台环境:linux");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Logger.Warning($"当前部署平台环境:OSX or FreeBSD");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
DicomSetupBuilder.UseServiceProvider(app.Services);
|
||||
|
||||
var logger = app.Services.GetService<Microsoft.Extensions.Logging.ILogger<Program>>();
|
||||
|
||||
var server = DicomServerFactory.Create<CStoreSCPService>(_configuration.GetSection("DicomSCPServiceConfig").GetValue<int>("ServerPort"), userState: app.Services, logger: logger);
|
||||
|
||||
|
||||
app.Run();
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:11224",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5127",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
using AutoMapper;
|
||||
using IRaCIS.Core.Application.Service.BusinessFilter;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Panda.DynamicWebApi;
|
||||
using Panda.DynamicWebApi.Attributes;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Service
|
||||
{
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
|
||||
#region 非泛型版本
|
||||
|
||||
[Authorize, DynamicWebApi, UnifiedApiResultFilter]
|
||||
public class BaseService : IBaseService, IDynamicWebApi
|
||||
{
|
||||
public IMapper _mapper { get; set; }
|
||||
|
||||
public IUserInfo _userInfo { get; set; }
|
||||
|
||||
|
||||
public IStringLocalizer _localizer { get; set; }
|
||||
|
||||
public IWebHostEnvironment _hostEnvironment { get; set; }
|
||||
|
||||
|
||||
|
||||
|
||||
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
|
||||
{
|
||||
return new ResponseOutput<string>()
|
||||
.NotOk($"The query object {typeof(TEntity).Name} does not exist , or was deleted by someone else, or an incorrect parameter query caused", code: ApiResponseCodeEnum.DataNotExist);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface IBaseService
|
||||
{
|
||||
[MemberNotNull(nameof(_mapper))]
|
||||
public IMapper _mapper { get; set; }
|
||||
|
||||
[MemberNotNull(nameof(_userInfo))]
|
||||
public IUserInfo _userInfo { get; set; }
|
||||
|
||||
[MemberNotNull(nameof(_localizer))]
|
||||
public IStringLocalizer _localizer { get; set; }
|
||||
|
||||
[MemberNotNull(nameof(_hostEnvironment))]
|
||||
public IWebHostEnvironment _hostEnvironment { get; set; }
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 泛型版本测试
|
||||
|
||||
|
||||
public interface IBaseServiceTest<T> where T : Entity
|
||||
{
|
||||
[MemberNotNull(nameof(_mapper))]
|
||||
public IMapper _mapper { get; set; }
|
||||
|
||||
[MemberNotNull(nameof(_userInfo))]
|
||||
public IUserInfo _userInfo { get; set; }
|
||||
|
||||
|
||||
|
||||
[MemberNotNull(nameof(_localizer))]
|
||||
public IStringLocalizer _localizer { get; set; }
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Authorize, DynamicWebApi, UnifiedApiResultFilter]
|
||||
public class BaseServiceTest<T> : IBaseServiceTest<T>, IDynamicWebApi where T : Entity
|
||||
{
|
||||
public IMapper _mapper { get; set; }
|
||||
|
||||
public IUserInfo _userInfo { get; set; }
|
||||
|
||||
public IStringLocalizer _localizer { get; set; }
|
||||
|
||||
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
|
||||
{
|
||||
return new ResponseOutput<string>()
|
||||
.NotOk($"The query object {typeof(TEntity).Name} does not exist , or was deleted by someone else, or an incorrect parameter query caused", code: ApiResponseCodeEnum.DataNotExist);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,376 @@
|
|||
using FellowOakDicom.Network;
|
||||
using FellowOakDicom;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using IRaCIS.Core.SCP.Service;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using Medallion.Threading;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using Serilog;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Data;
|
||||
using FellowOakDicom.Imaging;
|
||||
using SharpCompress.Common;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Service
|
||||
{
|
||||
|
||||
public class DicomSCPServiceOption
|
||||
{
|
||||
public List<string> CalledAEList { get; set; }
|
||||
|
||||
public string ServerPort { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public class CStoreSCPService : DicomService, IDicomServiceProvider, IDicomCStoreProvider, IDicomCEchoProvider
|
||||
{
|
||||
private IServiceProvider _serviceProvider { get; set; }
|
||||
|
||||
private List<Guid> _SCPStudyIdList { get; set; } = new List<Guid>();
|
||||
|
||||
private SCPImageUpload _upload { get; set; }
|
||||
|
||||
private Guid _trialId { get; set; }
|
||||
|
||||
private Guid _trialSiteId { get; set; }
|
||||
|
||||
|
||||
|
||||
private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]
|
||||
{
|
||||
DicomTransferSyntax.ExplicitVRLittleEndian,
|
||||
DicomTransferSyntax.ExplicitVRBigEndian,
|
||||
DicomTransferSyntax.ImplicitVRLittleEndian
|
||||
};
|
||||
|
||||
private static readonly DicomTransferSyntax[] _acceptedImageTransferSyntaxes = new DicomTransferSyntax[]
|
||||
{
|
||||
// Lossless
|
||||
DicomTransferSyntax.JPEGLSLossless, //1.2.840.10008.1.2.4.80
|
||||
DicomTransferSyntax.JPEG2000Lossless, //1.2.840.10008.1.2.4.90
|
||||
DicomTransferSyntax.JPEGProcess14SV1, //1.2.840.10008.1.2.4.70
|
||||
DicomTransferSyntax.JPEGProcess14, //1.2.840.10008.1.2.4.57 JPEG Lossless, Non-Hierarchical (Process 14)
|
||||
DicomTransferSyntax.RLELossless, //1.2.840.10008.1.2.5
|
||||
// Lossy
|
||||
DicomTransferSyntax.JPEGLSNearLossless,//1.2.840.10008.1.2.4.81"
|
||||
DicomTransferSyntax.JPEG2000Lossy, //1.2.840.10008.1.2.4.91
|
||||
DicomTransferSyntax.JPEGProcess1, //1.2.840.10008.1.2.4.50
|
||||
DicomTransferSyntax.JPEGProcess2_4, //1.2.840.10008.1.2.4.51
|
||||
// Uncompressed
|
||||
DicomTransferSyntax.ExplicitVRLittleEndian, //1.2.840.10008.1.2.1
|
||||
DicomTransferSyntax.ExplicitVRBigEndian, //1.2.840.10008.1.2.2
|
||||
DicomTransferSyntax.ImplicitVRLittleEndian //1.2.840.10008.1.2
|
||||
};
|
||||
|
||||
|
||||
public CStoreSCPService(INetworkStream stream, Encoding fallbackEncoding, Microsoft.Extensions.Logging.ILogger log, DicomServiceDependencies dependencies, IServiceProvider injectServiceProvider)
|
||||
: base(stream, fallbackEncoding, log, dependencies)
|
||||
{
|
||||
_serviceProvider = injectServiceProvider.CreateScope().ServiceProvider;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public Task OnReceiveAssociationRequestAsync(DicomAssociation association)
|
||||
{
|
||||
|
||||
_upload = new SCPImageUpload() { StartTime = DateTime.Now, CallingAE = association.CallingAE, CalledAE = association.CalledAE, CallingAEIP = association.RemoteHost };
|
||||
|
||||
|
||||
Log.Logger.Warning($"接收到来自{association.CallingAE}的连接");
|
||||
|
||||
//_serviceProvider = (IServiceProvider)this.UserState;
|
||||
|
||||
var _trialDicomAERepository = _serviceProvider.GetService<IRepository<TrialDicomAE>>();
|
||||
|
||||
|
||||
var trialDicomAEList = _trialDicomAERepository.Select(t => new { t.CalledAE, t.TrialId }).ToList();
|
||||
var trialCalledAEList = trialDicomAEList.Select(t => t.CalledAE).ToList();
|
||||
|
||||
Log.Logger.Information("当前系统配置:", string.Join('|', trialDicomAEList));
|
||||
|
||||
var findCalledAE = trialDicomAEList.Where(t => t.CalledAE == association.CalledAE).FirstOrDefault();
|
||||
|
||||
var isCanReceiveIamge = false;
|
||||
|
||||
if (findCalledAE != null)
|
||||
{
|
||||
_trialId = findCalledAE.TrialId;
|
||||
|
||||
var _trialSiteDicomAERepository = _serviceProvider.GetService<IRepository<TrialSiteDicomAE>>();
|
||||
|
||||
|
||||
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId==_trialId).FirstOrDefault();
|
||||
|
||||
if (findTrialSiteAE != null)
|
||||
{
|
||||
_trialSiteId = findTrialSiteAE.TrialSiteId;
|
||||
|
||||
isCanReceiveIamge = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (association.CallingAE == "test-callingAE")
|
||||
{
|
||||
isCanReceiveIamge = true;
|
||||
}
|
||||
|
||||
if (!trialCalledAEList.Contains(association.CalledAE) || isCanReceiveIamge == false)
|
||||
{
|
||||
|
||||
Log.Logger.Warning($"拒绝CallingAE:{association.CallingAE} CalledAE:{association.CalledAE}的连接");
|
||||
|
||||
return SendAssociationRejectAsync(
|
||||
DicomRejectResult.Permanent,
|
||||
DicomRejectSource.ServiceUser,
|
||||
DicomRejectReason.CalledAENotRecognized);
|
||||
}
|
||||
|
||||
foreach (var pc in association.PresentationContexts)
|
||||
{
|
||||
if (pc.AbstractSyntax == DicomUID.Verification)
|
||||
{
|
||||
pc.AcceptTransferSyntaxes(_acceptedTransferSyntaxes);
|
||||
}
|
||||
else if (pc.AbstractSyntax.StorageCategory != DicomStorageCategory.None)
|
||||
{
|
||||
pc.AcceptTransferSyntaxes(_acceptedImageTransferSyntaxes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return SendAssociationAcceptAsync(association);
|
||||
}
|
||||
|
||||
|
||||
public async Task OnReceiveAssociationReleaseRequestAsync()
|
||||
{
|
||||
await DataMaintenanceAsaync();
|
||||
|
||||
//记录监控
|
||||
|
||||
var _SCPImageUploadRepository = _serviceProvider.GetService<IRepository<SCPImageUpload>>();
|
||||
|
||||
_upload.EndTime = DateTime.Now;
|
||||
_upload.StudyCount = _SCPStudyIdList.Count;
|
||||
_upload.TrialId = _trialId;
|
||||
_upload.TrialSiteId = _trialSiteId;
|
||||
|
||||
await _SCPImageUploadRepository.AddAsync(_upload, true);
|
||||
|
||||
|
||||
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
|
||||
//将检查设置为传输结束
|
||||
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
|
||||
|
||||
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
|
||||
|
||||
await SendAssociationReleaseResponseAsync();
|
||||
}
|
||||
|
||||
|
||||
private async Task DataMaintenanceAsaync()
|
||||
{
|
||||
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}传输结束:开始维护数据,处理检查Modality");
|
||||
|
||||
|
||||
|
||||
//处理检查Modality
|
||||
var _dictionaryRepository = _serviceProvider.GetService<IRepository<Dictionary>>();
|
||||
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
|
||||
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
|
||||
|
||||
var dicModalityList = _dictionaryRepository.Where(t => t.Code == "Modality").SelectMany(t => t.ChildList.Select(c => c.Value)).ToList();
|
||||
var seriesModalityList = _seriesRepository.Where(t => _SCPStudyIdList.Contains(t.StudyId)).Select(t => new { SCPStudyId = t.StudyId, t.Modality }).ToList();
|
||||
|
||||
foreach (var g in seriesModalityList.GroupBy(t => t.SCPStudyId))
|
||||
{
|
||||
var modality = string.Join('、', g.Select(t => t.Modality).Distinct().ToList());
|
||||
|
||||
//特殊逻辑
|
||||
var modalityForEdit = dicModalityList.Contains(modality) ? modality : String.Empty;
|
||||
|
||||
if (modality == "MR")
|
||||
{
|
||||
modalityForEdit = "MRI";
|
||||
}
|
||||
|
||||
if (modality == "PT")
|
||||
{
|
||||
modalityForEdit = "PET";
|
||||
}
|
||||
if (modality == "PT、CT" || modality == "CT、PT")
|
||||
{
|
||||
modalityForEdit = "PET-CT";
|
||||
}
|
||||
|
||||
await _studyRepository.BatchUpdateNoTrackingAsync(t => t.Id == g.Key, u => new SCPStudy() { Modalities = modality, ModalityForEdit = modalityForEdit });
|
||||
|
||||
}
|
||||
|
||||
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}维护数据结束");
|
||||
}
|
||||
|
||||
public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason)
|
||||
{
|
||||
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}接收中断,中断原因:{source.ToString() + reason.ToString()}");
|
||||
/* nothing to do here */
|
||||
}
|
||||
|
||||
|
||||
public async void OnConnectionClosed(Exception exception)
|
||||
{
|
||||
/* nothing to do here */
|
||||
|
||||
//奇怪的bug 上传的时候,用王捷修改的影像,会关闭,重新连接,导致检查id 丢失,然后状态不一致
|
||||
if (exception == null)
|
||||
{
|
||||
//var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
|
||||
////将检查设置为传输结束
|
||||
//await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
|
||||
|
||||
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
|
||||
}
|
||||
|
||||
Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public async Task<DicomCStoreResponse> OnCStoreRequestAsync(DicomCStoreRequest request)
|
||||
{
|
||||
|
||||
string studyInstanceUid = request.Dataset.GetString(DicomTag.StudyInstanceUID);
|
||||
string seriesInstanceUid = request.Dataset.GetString(DicomTag.SeriesInstanceUID);
|
||||
string sopInstanceUid = request.Dataset.GetString(DicomTag.SOPInstanceUID);
|
||||
|
||||
//Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
|
||||
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, _trialId.ToString());
|
||||
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, _trialId.ToString());
|
||||
|
||||
|
||||
var ossService = _serviceProvider.GetService<IOSSService>();
|
||||
var dicomArchiveService = _serviceProvider.GetService<IDicomArchiveService>();
|
||||
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
|
||||
|
||||
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
|
||||
|
||||
var storeRelativePath = string.Empty;
|
||||
var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}/{studyInstanceUid}";
|
||||
|
||||
|
||||
long fileSize = 0;
|
||||
try
|
||||
{
|
||||
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
await request.File.SaveAsync(ms);
|
||||
|
||||
//irc 从路径最后一截取Guid
|
||||
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false);
|
||||
|
||||
fileSize = ms.Length;
|
||||
}
|
||||
|
||||
Log.Logger.Information($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} {request.SOPInstanceUID} 上传完成 ");
|
||||
|
||||
}
|
||||
catch (Exception ec)
|
||||
{
|
||||
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 上传异常 {ec.Message}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
|
||||
|
||||
using (await @lock.AcquireAsync())
|
||||
{
|
||||
try
|
||||
{
|
||||
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.Dataset, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize);
|
||||
|
||||
if (!_SCPStudyIdList.Contains(scpStudyId))
|
||||
{
|
||||
_SCPStudyIdList.Add(scpStudyId);
|
||||
}
|
||||
|
||||
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
|
||||
|
||||
//没有缩略图
|
||||
if (series != null && string.IsNullOrEmpty(series.ImageResizePath))
|
||||
{
|
||||
|
||||
// 生成缩略图
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
DicomImage image = new DicomImage(request.Dataset);
|
||||
|
||||
var sharpimage = image.RenderImage().AsSharpImage();
|
||||
sharpimage.Save(memoryStream, new JpegEncoder());
|
||||
|
||||
// 上传缩略图到 OSS
|
||||
|
||||
var seriesPath = await ossService.UploadToOSSAsync(memoryStream, ossFolderPath, seriesId.ToString() + ".preview.jpg", false);
|
||||
|
||||
Console.WriteLine(seriesPath + " Id: " + seriesId);
|
||||
|
||||
series.ImageResizePath = seriesPath;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await _seriesRepository.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//监控信息设置
|
||||
_upload.FileCount++;
|
||||
_upload.FileSize = _upload.FileSize + fileSize;
|
||||
return new DicomCStoreResponse(request, DicomStatus.Success);
|
||||
}
|
||||
|
||||
|
||||
public Task OnCStoreRequestExceptionAsync(string tempFileName, Exception e)
|
||||
{
|
||||
// let library handle logging and error response
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
public Task<DicomCEchoResponse> OnCEchoRequestAsync(DicomCEchoRequest request)
|
||||
{
|
||||
return Task.FromResult(new DicomCEchoResponse(request, DicomStatus.Success));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,356 @@
|
|||
using IRaCIS.Core.Domain.Share;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using Medallion.Threading;
|
||||
using FellowOakDicom;
|
||||
using FellowOakDicom.Imaging.Codec;
|
||||
using System.Data;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using FellowOakDicom.Network;
|
||||
using IRaCIS.Core.SCP.Service;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using MassTransit;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using Serilog.Sinks.File;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Service
|
||||
{
|
||||
public class DicomArchiveService : BaseService, IDicomArchiveService
|
||||
{
|
||||
private readonly IRepository<SCPPatient> _patientRepository;
|
||||
private readonly IRepository<SCPStudy> _studyRepository;
|
||||
private readonly IRepository<SCPSeries> _seriesRepository;
|
||||
private readonly IRepository<SCPInstance> _instanceRepository;
|
||||
private readonly IRepository<Dictionary> _dictionaryRepository;
|
||||
private readonly IDistributedLockProvider _distributedLockProvider;
|
||||
|
||||
|
||||
private List<Guid> _instanceIdList = new List<Guid>();
|
||||
|
||||
public DicomArchiveService(IRepository<SCPPatient> patientRepository, IRepository<SCPStudy> studyRepository,
|
||||
IRepository<SCPSeries> seriesRepository,
|
||||
IRepository<SCPInstance> instanceRepository,
|
||||
IRepository<Dictionary> dictionaryRepository,
|
||||
IDistributedLockProvider distributedLockProvider)
|
||||
{
|
||||
_distributedLockProvider = distributedLockProvider;
|
||||
_studyRepository = studyRepository;
|
||||
_patientRepository = patientRepository;
|
||||
_seriesRepository = seriesRepository;
|
||||
_instanceRepository = instanceRepository;
|
||||
_dictionaryRepository = dictionaryRepository;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 单个文件接收 归档
|
||||
/// </summary>
|
||||
/// <param name="dataset"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public async Task<Guid> ArchiveDicomFileAsync(DicomDataset dataset, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE,long fileSize)
|
||||
{
|
||||
string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
|
||||
string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID);
|
||||
string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID);
|
||||
|
||||
string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID,string.Empty);
|
||||
|
||||
//Guid patientId= IdentifierHelper.CreateGuid(patientIdStr);
|
||||
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid,trialId.ToString());
|
||||
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, trialId.ToString());
|
||||
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, trialId.ToString());
|
||||
|
||||
var isStudyNeedAdd = false;
|
||||
var isSeriesNeedAdd = false;
|
||||
var isInstanceNeedAdd = false;
|
||||
var isPatientNeedAdd = false;
|
||||
|
||||
//var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
|
||||
|
||||
//using (@lock.Acquire())
|
||||
{
|
||||
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId==trialSiteId );
|
||||
var findStudy = await _studyRepository.FirstOrDefaultAsync(t=>t.Id== studyId);
|
||||
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
|
||||
var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId);
|
||||
|
||||
DateTime? studyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.StudyDate).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.StudyTime).TimeOfDay);
|
||||
|
||||
//先传输了修改了患者编号的,又传输了没有修改患者编号的,导致后传输的没有修改患者编号的下面的检查为0
|
||||
if (findPatient == null && findStudy==null)
|
||||
{
|
||||
isPatientNeedAdd = true;
|
||||
|
||||
|
||||
findPatient = new SCPPatient()
|
||||
{
|
||||
Id = NewId.NextSequentialGuid(),
|
||||
TrialId=trialId,
|
||||
TrialSiteId=trialSiteId,
|
||||
PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
|
||||
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
|
||||
PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty),
|
||||
PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty),
|
||||
PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty),
|
||||
|
||||
EarliestStudyTime = studyTime,
|
||||
LatestStudyTime = studyTime,
|
||||
LatestPushTime = DateTime.Now,
|
||||
};
|
||||
|
||||
if (findPatient.PatientBirthDate.Length == 8)
|
||||
{
|
||||
var birthDateStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}-{findPatient.PatientBirthDate[4]}{findPatient.PatientBirthDate[5]}-{findPatient.PatientBirthDate[6]}{findPatient.PatientBirthDate[7]}";
|
||||
|
||||
var yearStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}";
|
||||
|
||||
int year = 0;
|
||||
|
||||
var canParse = int.TryParse(yearStr, out year);
|
||||
|
||||
if (canParse && year > 1900)
|
||||
{
|
||||
findPatient.PatientBirthDate = birthDateStr;
|
||||
|
||||
DateTime birthDate;
|
||||
|
||||
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate,out birthDate))
|
||||
{
|
||||
var patientAge = studyTime.Value.Year - birthDate.Year;
|
||||
// 如果生日还未到,年龄减去一岁
|
||||
if (studyTime.Value < birthDate.AddYears(patientAge))
|
||||
{
|
||||
patientAge--;
|
||||
}
|
||||
|
||||
findPatient.PatientAge = patientAge.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
findPatient.PatientBirthDate = string.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (studyTime < findPatient.EarliestStudyTime)
|
||||
{
|
||||
findPatient.EarliestStudyTime = studyTime;
|
||||
}
|
||||
if (studyTime > findPatient.LatestStudyTime)
|
||||
{
|
||||
findPatient.LatestStudyTime = studyTime;
|
||||
}
|
||||
|
||||
findPatient.LatestPushTime = DateTime.Now;
|
||||
}
|
||||
|
||||
if (findStudy == null)
|
||||
{
|
||||
isStudyNeedAdd = true;
|
||||
findStudy = new SCPStudy
|
||||
{
|
||||
CalledAE = calledAE,
|
||||
CallingAE = callingAE,
|
||||
|
||||
PatientId = findPatient.Id,
|
||||
Id = studyId,
|
||||
TrialId = trialId,
|
||||
TrialSiteId = trialSiteId,
|
||||
StudyInstanceUid = studyInstanceUid,
|
||||
StudyTime = studyTime,
|
||||
Modalities = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
|
||||
//ModalityForEdit = modalityForEdit,
|
||||
Description = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty),
|
||||
InstitutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty),
|
||||
PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
|
||||
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
|
||||
PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty),
|
||||
PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty),
|
||||
BodyPartExamined = dataset.GetSingleValueOrDefault(DicomTag.BodyPartExamined, string.Empty),
|
||||
|
||||
StudyId = dataset.GetSingleValueOrDefault(DicomTag.StudyID, string.Empty),
|
||||
AccessionNumber = dataset.GetSingleValueOrDefault(DicomTag.AccessionNumber, string.Empty),
|
||||
|
||||
//需要特殊处理
|
||||
PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty),
|
||||
|
||||
|
||||
AcquisitionTime = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionTime, string.Empty),
|
||||
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
|
||||
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
|
||||
|
||||
|
||||
|
||||
//IsDoubleReview = addtionalInfo.IsDoubleReview,
|
||||
SeriesCount = 0,
|
||||
InstanceCount = 0
|
||||
};
|
||||
|
||||
|
||||
if (findStudy.PatientBirthDate.Length == 8)
|
||||
{
|
||||
findStudy.PatientBirthDate = $"{findStudy.PatientBirthDate[0]}{findStudy.PatientBirthDate[1]}{findStudy.PatientBirthDate[2]}{findStudy.PatientBirthDate[3]}-{findStudy.PatientBirthDate[4]}{findStudy.PatientBirthDate[5]}-{findStudy.PatientBirthDate[6]}{findStudy.PatientBirthDate[7]}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (findSerice == null)
|
||||
{
|
||||
isSeriesNeedAdd = true;
|
||||
|
||||
findSerice = new SCPSeries
|
||||
{
|
||||
Id = seriesId,
|
||||
StudyId = findStudy.Id,
|
||||
|
||||
StudyInstanceUid = findStudy.StudyInstanceUid,
|
||||
SeriesInstanceUid = seriesInstanceUid,
|
||||
SeriesNumber = dataset.GetSingleValueOrDefault(DicomTag.SeriesNumber, 1),
|
||||
//SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, DateTime.Now).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, DateTime.Now).TimeOfDay),
|
||||
//SeriesTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.SeriesDate) + dataset.GetSingleValue<string>(DicomTag.SeriesTime), out DateTime dt) ? dt : null,
|
||||
SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.SeriesDate).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.SeriesTime).TimeOfDay),
|
||||
Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
|
||||
Description = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty),
|
||||
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
|
||||
|
||||
ImagePositionPatient = dataset.GetSingleValueOrDefault(DicomTag.ImagePositionPatient, string.Empty),
|
||||
ImageOrientationPatient = dataset.GetSingleValueOrDefault(DicomTag.ImageOrientationPatient, string.Empty),
|
||||
BodyPartExamined = dataset.GetSingleValueOrDefault(DicomTag.BodyPartExamined, string.Empty),
|
||||
SequenceName = dataset.GetSingleValueOrDefault(DicomTag.SequenceName, string.Empty),
|
||||
ProtocolName = dataset.GetSingleValueOrDefault(DicomTag.ProtocolName, string.Empty),
|
||||
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
|
||||
|
||||
AcquisitionTime = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionTime, string.Empty),
|
||||
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
|
||||
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
|
||||
|
||||
|
||||
InstanceCount = 0
|
||||
};
|
||||
|
||||
++findStudy.SeriesCount;
|
||||
}
|
||||
|
||||
|
||||
if (findInstance == null)
|
||||
{
|
||||
isInstanceNeedAdd = true;
|
||||
findInstance = new SCPInstance
|
||||
{
|
||||
Id = instanceId,
|
||||
StudyId = findStudy.Id,
|
||||
SeriesId = findSerice.Id,
|
||||
StudyInstanceUid = findStudy.StudyInstanceUid,
|
||||
SeriesInstanceUid = findSerice.SeriesInstanceUid,
|
||||
|
||||
SopInstanceUid = sopInstanceUid,
|
||||
InstanceNumber = dataset.GetSingleValueOrDefault(DicomTag.InstanceNumber, 1),
|
||||
InstanceTime = dataset.GetSingleValueOrDefault(DicomTag.ContentDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.ContentDate).Add(dataset.GetSingleValueOrDefault(DicomTag.ContentTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.ContentTime).TimeOfDay),
|
||||
//InstanceTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.ContentDate) + dataset.GetSingleValue<string>(DicomTag.ContentTime), out DateTime dt) ? dt : null,
|
||||
//InstanceTime = dataset.GetSingleValueOrDefault(DicomTag.ContentDate,(DateTime?)null)?.Add(dataset.GetSingleValueOrDefault(DicomTag.ContentTime, TimeSpan.Zero)),
|
||||
//dataset.GetSingleValueOrDefault(DicomTag.ContentDate,DateTime.Now);//, DicomTag.ContentTime)
|
||||
CPIStatus = false,
|
||||
ImageRows = dataset.GetSingleValueOrDefault(DicomTag.Rows, 0),
|
||||
ImageColumns = dataset.GetSingleValueOrDefault(DicomTag.Columns, 0),
|
||||
SliceLocation = dataset.GetSingleValueOrDefault(DicomTag.SliceLocation, 0),
|
||||
|
||||
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
|
||||
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 0),
|
||||
PixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.PixelSpacing, string.Empty),
|
||||
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
|
||||
FrameOfReferenceUID = dataset.GetSingleValueOrDefault(DicomTag.FrameOfReferenceUID, string.Empty),
|
||||
WindowCenter = dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, string.Empty),
|
||||
WindowWidth = dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, string.Empty),
|
||||
|
||||
Path = fileRelativePath,
|
||||
|
||||
FileSize= fileSize,
|
||||
|
||||
};
|
||||
|
||||
++findStudy.InstanceCount;
|
||||
++findSerice.InstanceCount;
|
||||
}
|
||||
|
||||
if (isPatientNeedAdd)
|
||||
{
|
||||
var ss = await _patientRepository.AddAsync(findPatient);
|
||||
}
|
||||
if (isStudyNeedAdd)
|
||||
{
|
||||
var dd = await _studyRepository.AddAsync(findStudy);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _studyRepository.BatchUpdateNoTrackingAsync(t => t.Id == findStudy.Id, t => new SCPStudy() { IsUploadFinished = false });
|
||||
}
|
||||
|
||||
if (isSeriesNeedAdd)
|
||||
{
|
||||
await _seriesRepository.AddAsync(findSerice);
|
||||
}
|
||||
if (isInstanceNeedAdd)
|
||||
{
|
||||
await _instanceRepository.AddAsync(findInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath,FileSize=fileSize });
|
||||
}
|
||||
|
||||
await _studyRepository.SaveChangesAsync();
|
||||
|
||||
return findStudy.Id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 从DICOM文件中获取使用的字符集
|
||||
private string GetEncodingVaulueFromDicomFile(DicomDataset dataset, DicomTag dicomTag)
|
||||
{
|
||||
|
||||
// 获取DICOM文件的特定元素,通常用于指示使用的字符集
|
||||
var charset = dataset.GetSingleValueOrDefault(DicomTag.SpecificCharacterSet, string.Empty);
|
||||
|
||||
var dicomEncoding = DicomEncoding.GetEncoding(charset);
|
||||
|
||||
|
||||
var dicomStringElement = dataset.GetDicomItem<DicomStringElement>(dicomTag);
|
||||
|
||||
var bytes = dicomStringElement.Buffer.Data;
|
||||
|
||||
|
||||
return dicomEncoding.GetString(bytes);
|
||||
|
||||
|
||||
//// 从DICOM文件中获取使用的字符集
|
||||
//string filePath = "C:\\Users\\hang\\Documents\\WeChat Files\\wxid_r2imdzb7j3q922\\FileStorage\\File\\2024-05\\1.2.840.113619.2.80.169103990.5390.1271401378.4.dcm";
|
||||
//DicomFile dicomFile = DicomFile.Open(filePath);
|
||||
|
||||
//// 获取DICOM文件的特定元素,通常用于指示使用的字符集
|
||||
//var charset = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SpecificCharacterSet, string.Empty);
|
||||
|
||||
//var dicomEncoding = DicomEncoding.GetEncoding(charset);
|
||||
|
||||
//var value = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
|
||||
|
||||
//var dicomStringElement = dicomFile.Dataset.GetDicomItem<DicomStringElement>(DicomTag.PatientName);
|
||||
|
||||
//var bytes = dicomStringElement.Buffer.Data;
|
||||
|
||||
//var aa= dicomEncoding.GetString(bytes);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using FellowOakDicom;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Service
|
||||
{
|
||||
public interface IDicomArchiveService
|
||||
{
|
||||
Task<Guid> ArchiveDicomFileAsync(DicomDataset dicomDataset,Guid trialId,Guid trialSiteId, string fileRelativePath,string callingAE,string calledAE,long fileSize);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,770 @@
|
|||
using AlibabaCloud.SDK.Sts20150401;
|
||||
using Aliyun.OSS;
|
||||
using Amazon;
|
||||
using Amazon.Runtime;
|
||||
using Amazon.S3;
|
||||
using Amazon.S3.Model;
|
||||
using Amazon.SecurityToken;
|
||||
using Amazon.SecurityToken.Model;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.NewtonsoftJson;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Minio;
|
||||
using Minio.DataModel.Args;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace IRaCIS.Core.SCP;
|
||||
|
||||
#region 绑定和返回模型
|
||||
|
||||
[LowerCamelCaseJson]
|
||||
public class MinIOOptions : AWSOptions
|
||||
{
|
||||
public int Port { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class AWSOptions
|
||||
{
|
||||
public string EndPoint { get; set; }
|
||||
public bool UseSSL { get; set; }
|
||||
public string AccessKeyId { get; set; }
|
||||
public string RoleArn { get; set; }
|
||||
public string SecretAccessKey { get; set; }
|
||||
public string BucketName { get; set; }
|
||||
public string ViewEndpoint { get; set; }
|
||||
public int DurationSeconds { get; set; }
|
||||
public string Region { get; set; }
|
||||
}
|
||||
|
||||
public class AliyunOSSOptions
|
||||
{
|
||||
public string RegionId { get; set; }
|
||||
public string AccessKeyId { get; set; }
|
||||
public string AccessKeySecret { get; set; }
|
||||
|
||||
public string InternalEndpoint { get; set; }
|
||||
|
||||
public string EndPoint { get; set; }
|
||||
public string BucketName { get; set; }
|
||||
|
||||
public string RoleArn { get; set; }
|
||||
|
||||
public string Region { get; set; }
|
||||
|
||||
public string ViewEndpoint { get; set; }
|
||||
|
||||
public int DurationSeconds { get; set; }
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class ObjectStoreServiceOptions
|
||||
{
|
||||
public string ObjectStoreUse { get; set; }
|
||||
|
||||
public AliyunOSSOptions AliyunOSS { get; set; }
|
||||
|
||||
|
||||
public MinIOOptions MinIO { get; set; }
|
||||
|
||||
public AWSOptions AWS { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class ObjectStoreDTO
|
||||
{
|
||||
public string ObjectStoreUse { get; set; }
|
||||
|
||||
|
||||
public AliyunOSSTempToken AliyunOSS { get; set; }
|
||||
|
||||
public MinIOOptions MinIO { get; set; }
|
||||
|
||||
public AWSTempToken AWS { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[LowerCamelCaseJson]
|
||||
public class AliyunOSSTempToken
|
||||
{
|
||||
public string AccessKeyId { get; set; }
|
||||
public string AccessKeySecret { get; set; }
|
||||
|
||||
public string EndPoint { get; set; }
|
||||
public string BucketName { get; set; }
|
||||
|
||||
public string Region { get; set; }
|
||||
|
||||
public string ViewEndpoint { get; set; }
|
||||
|
||||
public string SecurityToken { get; set; }
|
||||
public DateTime Expiration { get; set; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
[LowerCamelCaseJson]
|
||||
public class AWSTempToken
|
||||
{
|
||||
public string Region { get; set; }
|
||||
public string SessionToken { get; set; }
|
||||
public string EndPoint { get; set; }
|
||||
public string AccessKeyId { get; set; }
|
||||
public string SecretAccessKey { get; set; }
|
||||
public string BucketName { get; set; }
|
||||
public string ViewEndpoint { get; set; }
|
||||
public DateTime Expiration { get; set; }
|
||||
}
|
||||
|
||||
public enum ObjectStoreUse
|
||||
{
|
||||
AliyunOSS = 0,
|
||||
MinIO = 1,
|
||||
AWS = 2,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// aws 参考链接 https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/dotnetv3/S3/S3_Basics
|
||||
|
||||
public interface IOSSService
|
||||
{
|
||||
public Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true);
|
||||
public Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true);
|
||||
|
||||
public Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath);
|
||||
|
||||
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
|
||||
|
||||
public Task<string> GetSignedUrl(string ossRelativePath);
|
||||
|
||||
public Task DeleteFromPrefix(string prefix);
|
||||
|
||||
public ObjectStoreDTO GetObjectStoreTempToken();
|
||||
}
|
||||
|
||||
|
||||
public class OSSService : IOSSService
|
||||
{
|
||||
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
|
||||
|
||||
private AliyunOSSTempToken AliyunOSSTempToken { get; set; }
|
||||
|
||||
private AWSTempToken AWSTempToken { get; set; }
|
||||
|
||||
|
||||
public OSSService(IOptionsMonitor<ObjectStoreServiceOptions> options)
|
||||
{
|
||||
ObjectStoreServiceOptions = options.CurrentValue;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
|
||||
/// </summary>
|
||||
/// <param name="fileStream"></param>
|
||||
/// <param name="oosFolderPath"></param>
|
||||
/// <param name="fileRealName"></param>
|
||||
/// <param name="isFileNameAddGuid"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
|
||||
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{fileRealName}" : $"{oosFolderPath}/{fileRealName}";
|
||||
|
||||
try
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
fileStream.CopyTo(memoryStream);
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
||||
|
||||
|
||||
// 上传文件
|
||||
var result = _ossClient.PutObject(aliConfig.BucketName, ossRelativePath, memoryStream);
|
||||
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
|
||||
{
|
||||
var minIOConfig = ObjectStoreServiceOptions.MinIO;
|
||||
|
||||
|
||||
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
|
||||
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
|
||||
.Build();
|
||||
|
||||
var putObjectArgs = new PutObjectArgs()
|
||||
.WithBucket(minIOConfig.BucketName)
|
||||
.WithObject(ossRelativePath)
|
||||
.WithStreamData(memoryStream)
|
||||
.WithObjectSize(memoryStream.Length);
|
||||
|
||||
await minioClient.PutObjectAsync(putObjectArgs);
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
|
||||
|
||||
|
||||
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
var putObjectRequest = new Amazon.S3.Model.PutObjectRequest()
|
||||
{
|
||||
BucketName = awsConfig.BucketName,
|
||||
InputStream = memoryStream,
|
||||
Key = ossRelativePath,
|
||||
};
|
||||
|
||||
await amazonS3Client.PutObjectAsync(putObjectRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw new BusinessValidationFailedException($"上传发生异常:{ex.Message}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return "/" + ossRelativePath;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
|
||||
/// </summary>
|
||||
/// <param name="localFilePath"></param>
|
||||
/// <param name="oosFolderPath"></param>
|
||||
/// <param name="isFileNameAddGuid"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BusinessValidationFailedException"></exception>
|
||||
public async Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
|
||||
var localFileName = Path.GetFileName(localFilePath);
|
||||
|
||||
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{localFileName}" : $"{oosFolderPath}/{localFileName}";
|
||||
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
||||
// 上传文件
|
||||
var result = _ossClient.PutObject(aliConfig.BucketName, ossRelativePath, localFilePath);
|
||||
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
|
||||
{
|
||||
var minIOConfig = ObjectStoreServiceOptions.MinIO;
|
||||
|
||||
|
||||
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
|
||||
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
|
||||
.Build();
|
||||
|
||||
var putObjectArgs = new PutObjectArgs()
|
||||
.WithBucket(minIOConfig.BucketName)
|
||||
.WithObject(ossRelativePath)
|
||||
.WithFileName(localFilePath);
|
||||
|
||||
await minioClient.PutObjectAsync(putObjectArgs);
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
|
||||
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
|
||||
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
var putObjectRequest = new Amazon.S3.Model.PutObjectRequest()
|
||||
{
|
||||
BucketName = awsConfig.BucketName,
|
||||
FilePath = localFilePath,
|
||||
Key = ossRelativePath,
|
||||
};
|
||||
|
||||
await amazonS3Client.PutObjectAsync(putObjectRequest);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
return "/" + ossRelativePath;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public async Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
|
||||
ossRelativePath = ossRelativePath.TrimStart('/');
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
||||
// 上传文件
|
||||
var result = _ossClient.GetObject(aliConfig.BucketName, ossRelativePath);
|
||||
|
||||
// 将下载的文件流保存到本地文件
|
||||
using (var fs = File.OpenWrite(localFilePath))
|
||||
{
|
||||
result.Content.CopyTo(fs);
|
||||
fs.Close();
|
||||
}
|
||||
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
|
||||
{
|
||||
var minIOConfig = ObjectStoreServiceOptions.MinIO;
|
||||
|
||||
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
|
||||
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
|
||||
.Build();
|
||||
|
||||
var getObjectArgs = new GetObjectArgs()
|
||||
.WithBucket(minIOConfig.BucketName)
|
||||
.WithObject(ossRelativePath)
|
||||
.WithFile(localFilePath);
|
||||
|
||||
await minioClient.GetObjectAsync(getObjectArgs);
|
||||
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
|
||||
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
|
||||
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
var getObjectArgs = new Amazon.S3.Model.GetObjectRequest()
|
||||
{
|
||||
BucketName = awsConfig.BucketName,
|
||||
Key = ossRelativePath,
|
||||
};
|
||||
|
||||
|
||||
await (await amazonS3Client.GetObjectAsync(getObjectArgs)).WriteResponseStreamToFileAsync(localFilePath, true, CancellationToken.None);
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw new BusinessValidationFailedException("oss下载失败!" + ex.Message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public async Task<string> GetSignedUrl(string ossRelativePath)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
|
||||
ossRelativePath = ossRelativePath.TrimStart('/');
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
||||
// 生成签名URL。
|
||||
var req = new GeneratePresignedUriRequest(aliConfig.BucketName, ossRelativePath, SignHttpMethod.Get)
|
||||
{
|
||||
// 设置签名URL过期时间,默认值为3600秒。
|
||||
Expiration = DateTime.Now.AddHours(1),
|
||||
};
|
||||
var uri = _ossClient.GeneratePresignedUri(req);
|
||||
|
||||
return uri.PathAndQuery;
|
||||
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
|
||||
{
|
||||
var minIOConfig = ObjectStoreServiceOptions.MinIO;
|
||||
|
||||
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
|
||||
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
|
||||
.Build();
|
||||
|
||||
|
||||
var args = new PresignedGetObjectArgs()
|
||||
.WithBucket(minIOConfig.BucketName)
|
||||
.WithObject(ossRelativePath)
|
||||
.WithExpiry(3600)
|
||||
/*.WithHeaders(reqParams)*/;
|
||||
|
||||
var presignedUrl = await minioClient.PresignedGetObjectAsync(args);
|
||||
|
||||
Uri uri = new Uri(presignedUrl);
|
||||
|
||||
string relativePath = uri.PathAndQuery;
|
||||
|
||||
|
||||
return relativePath;
|
||||
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
|
||||
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
|
||||
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
|
||||
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
var presignedUrl = await amazonS3Client.GetPreSignedURLAsync(new GetPreSignedUrlRequest()
|
||||
{
|
||||
BucketName = awsConfig.BucketName,
|
||||
Key = ossRelativePath,
|
||||
Expires = DateTime.UtcNow.AddMinutes(120)
|
||||
});
|
||||
|
||||
Uri uri = new Uri(presignedUrl);
|
||||
|
||||
string relativePath = uri.PathAndQuery;
|
||||
|
||||
|
||||
return relativePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw new BusinessValidationFailedException("oss授权url失败!" + ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除某个目录的文件
|
||||
/// </summary>
|
||||
/// <param name="prefix"></param>
|
||||
/// <returns></returns>
|
||||
public async Task DeleteFromPrefix(string prefix)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
ObjectListing objectListing = null;
|
||||
string nextMarker = null;
|
||||
do
|
||||
{
|
||||
// 使用 prefix 模拟目录结构,设置 MaxKeys 和 NextMarker
|
||||
objectListing = _ossClient.ListObjects(new Aliyun.OSS.ListObjectsRequest(aliConfig.BucketName)
|
||||
{
|
||||
Prefix = prefix,
|
||||
MaxKeys = 1000,
|
||||
Marker = nextMarker
|
||||
});
|
||||
|
||||
List<string> keys = objectListing.ObjectSummaries.Select(t => t.Key).ToList();
|
||||
|
||||
// 删除获取到的文件
|
||||
if (keys.Count > 0)
|
||||
{
|
||||
_ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(aliConfig.BucketName, keys, false));
|
||||
}
|
||||
|
||||
// 设置 NextMarker 以获取下一页的数据
|
||||
nextMarker = objectListing.NextMarker;
|
||||
|
||||
} while (objectListing.IsTruncated);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: {ex.Message}");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
|
||||
{
|
||||
var minIOConfig = ObjectStoreServiceOptions.MinIO;
|
||||
|
||||
|
||||
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
|
||||
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
|
||||
.Build();
|
||||
|
||||
|
||||
var listArgs = new ListObjectsArgs().WithBucket(minIOConfig.BucketName).WithPrefix(prefix).WithRecursive(true);
|
||||
|
||||
|
||||
|
||||
// 创建一个空列表用于存储对象键
|
||||
var objects = new List<string>();
|
||||
|
||||
// 使用 await foreach 来异步迭代对象列表
|
||||
await foreach (var item in minioClient.ListObjectsEnumAsync(listArgs))
|
||||
{
|
||||
objects.Add(item.Key);
|
||||
}
|
||||
|
||||
|
||||
if (objects.Count > 0)
|
||||
{
|
||||
var objArgs = new RemoveObjectsArgs()
|
||||
.WithBucket(minIOConfig.BucketName)
|
||||
.WithObjects(objects);
|
||||
|
||||
// 删除对象
|
||||
await minioClient.RemoveObjectsAsync(objArgs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
|
||||
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
|
||||
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
|
||||
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
// 列出指定前缀下的所有对象
|
||||
var listObjectsRequest = new ListObjectsV2Request
|
||||
{
|
||||
BucketName = awsConfig.BucketName,
|
||||
Prefix = prefix
|
||||
};
|
||||
|
||||
var listObjectsResponse = await amazonS3Client.ListObjectsV2Async(listObjectsRequest);
|
||||
|
||||
if (listObjectsResponse.S3Objects.Count > 0)
|
||||
{
|
||||
// 准备删除请求
|
||||
var deleteObjectsRequest = new Amazon.S3.Model.DeleteObjectsRequest
|
||||
{
|
||||
BucketName = awsConfig.BucketName,
|
||||
Objects = new List<KeyVersion>()
|
||||
};
|
||||
|
||||
foreach (var s3Object in listObjectsResponse.S3Objects)
|
||||
{
|
||||
deleteObjectsRequest.Objects.Add(new KeyVersion
|
||||
{
|
||||
Key = s3Object.Key
|
||||
});
|
||||
}
|
||||
|
||||
// 批量删除对象
|
||||
var deleteObjectsResponse = await amazonS3Client.DeleteObjectsAsync(deleteObjectsRequest);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ObjectStoreDTO GetObjectStoreTempToken()
|
||||
{
|
||||
|
||||
var ossOptions = ObjectStoreServiceOptions.AliyunOSS;
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
var client = new Client(new AlibabaCloud.OpenApiClient.Models.Config()
|
||||
{
|
||||
AccessKeyId = ossOptions.AccessKeyId,
|
||||
AccessKeySecret = ossOptions.AccessKeySecret,
|
||||
//AccessKeyId = "LTAI5tJV76pYX5yPg1N9QVE8",
|
||||
//AccessKeySecret = "roRNLa9YG1of4pYruJGCNKBXEWTAWa",
|
||||
|
||||
Endpoint = "sts.cn-hangzhou.aliyuncs.com"
|
||||
});
|
||||
|
||||
var assumeRoleRequest = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest();
|
||||
// 将<YOUR_ROLE_SESSION_NAME>设置为自定义的会话名称,例如oss-role-session。
|
||||
assumeRoleRequest.RoleSessionName = $"session-name-{NewId.NextGuid()}";
|
||||
// 将<YOUR_ROLE_ARN>替换为拥有上传文件到指定OSS Bucket权限的RAM角色的ARN。
|
||||
assumeRoleRequest.RoleArn = ossOptions.RoleArn;
|
||||
//assumeRoleRequest.RoleArn = "acs:ram::1899121822495495:role/webdirect";
|
||||
assumeRoleRequest.DurationSeconds = ossOptions.DurationSeconds;
|
||||
var runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions();
|
||||
var response = client.AssumeRoleWithOptions(assumeRoleRequest, runtime);
|
||||
var credentials = response.Body.Credentials;
|
||||
|
||||
var tempToken = new AliyunOSSTempToken()
|
||||
{
|
||||
AccessKeyId = credentials.AccessKeyId,
|
||||
AccessKeySecret = credentials.AccessKeySecret,
|
||||
|
||||
//转为服务器时区,最后统一转为客户端时区
|
||||
Expiration = TimeZoneInfo.ConvertTimeFromUtc(DateTime.Parse(credentials.Expiration), TimeZoneInfo.Local),
|
||||
SecurityToken = credentials.SecurityToken,
|
||||
|
||||
|
||||
Region = ossOptions.Region,
|
||||
BucketName = ossOptions.BucketName,
|
||||
EndPoint = ossOptions.EndPoint,
|
||||
ViewEndpoint = ossOptions.ViewEndpoint,
|
||||
|
||||
};
|
||||
|
||||
AliyunOSSTempToken = tempToken;
|
||||
|
||||
return new ObjectStoreDTO() { ObjectStoreUse = ObjectStoreServiceOptions.ObjectStoreUse, AliyunOSS = tempToken };
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
|
||||
{
|
||||
return new ObjectStoreDTO() { ObjectStoreUse = ObjectStoreServiceOptions.ObjectStoreUse, MinIO = ObjectStoreServiceOptions.MinIO };
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
var awsOptions = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
//aws 临时凭证
|
||||
// 创建 STS 客户端
|
||||
var stsClient = new AmazonSecurityTokenServiceClient(awsOptions.AccessKeyId, awsOptions.SecretAccessKey);
|
||||
|
||||
// 使用 AssumeRole 请求临时凭证
|
||||
var assumeRoleRequest = new AssumeRoleRequest
|
||||
{
|
||||
|
||||
RoleArn = awsOptions.RoleArn, // 角色 ARN
|
||||
RoleSessionName = $"session-name-{NewId.NextGuid()}",
|
||||
DurationSeconds = awsOptions.DurationSeconds // 临时凭证有效期
|
||||
};
|
||||
|
||||
var assumeRoleResponse = stsClient.AssumeRoleAsync(assumeRoleRequest).Result;
|
||||
|
||||
var credentials = assumeRoleResponse.Credentials;
|
||||
|
||||
var tempToken = new AWSTempToken()
|
||||
{
|
||||
AccessKeyId = credentials.AccessKeyId,
|
||||
SecretAccessKey = credentials.SecretAccessKey,
|
||||
SessionToken = credentials.SessionToken,
|
||||
Expiration = credentials.Expiration,
|
||||
Region = awsOptions.Region,
|
||||
BucketName = awsOptions.BucketName,
|
||||
EndPoint = awsOptions.EndPoint,
|
||||
ViewEndpoint = awsOptions.ViewEndpoint,
|
||||
|
||||
};
|
||||
|
||||
AWSTempToken = tempToken;
|
||||
return new ObjectStoreDTO() { ObjectStoreUse = ObjectStoreServiceOptions.ObjectStoreUse, AWS = tempToken };
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
|
|
@ -7,16 +7,16 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.4" />
|
||||
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.5" />
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.405" />
|
||||
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.400.36" />
|
||||
<PackageReference Include="DistributedLock.Core" Version="1.0.7" />
|
||||
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.5" />
|
||||
<PackageReference Include="fo-dicom" Version="5.1.3" />
|
||||
<PackageReference Include="fo-dicom.Codecs" Version="5.15.1" />
|
||||
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.1.3" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.416.8" />
|
||||
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.401.81" />
|
||||
<PackageReference Include="DistributedLock.Core" Version="1.0.8" />
|
||||
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" />
|
||||
<PackageReference Include="fo-dicom" Version="5.2.4" />
|
||||
<PackageReference Include="fo-dicom.Codecs" Version="5.16.4" />
|
||||
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
<PackageReference Include="Minio" Version="6.0.3" />
|
||||
|
|
@ -25,10 +25,10 @@
|
|||
</PackageReference>
|
||||
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
|
||||
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -126,16 +126,22 @@ builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
|||
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
||||
});
|
||||
|
||||
|
||||
builder.Services.AddFellowOakDicom().AddTranscoderManager<NativeTranscoderManager>()
|
||||
//.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
|
||||
.AddImageManager<ImageSharpImageManager>();
|
||||
|
||||
|
||||
//Dicom影像渲染图片 跨平台
|
||||
//builder.Services.AddDicomSetup();
|
||||
new DicomSetupBuilder()
|
||||
.RegisterServices(s =>
|
||||
s.AddFellowOakDicom()
|
||||
.AddTranscoderManager<NativeTranscoderManager>()
|
||||
//.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
|
||||
.AddImageManager<ImageSharpImageManager>())
|
||||
.SkipValidation()
|
||||
.Build();
|
||||
//new DicomSetupBuilder()
|
||||
// .RegisterServices(s =>
|
||||
// s.AddFellowOakDicom()
|
||||
// .AddTranscoderManager<NativeTranscoderManager>()
|
||||
// //.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
|
||||
// .AddImageManager<ImageSharpImageManager>())
|
||||
// .SkipValidation()
|
||||
// .Build();
|
||||
|
||||
|
||||
|
||||
|
|
@ -211,6 +217,8 @@ else
|
|||
|
||||
#endregion
|
||||
|
||||
DicomSetupBuilder.UseServiceProvider(app.Services);
|
||||
|
||||
var logger = app.Services.GetService<Microsoft.Extensions.Logging.ILogger<Program>>();
|
||||
|
||||
var server = DicomServerFactory.Create<CStoreSCPService>(_configuration.GetSection("DicomSCPServiceConfig").GetValue<int>("ServerPort"), userState: app.Services,logger: logger);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ using FellowOakDicom.Imaging;
|
|||
using SharpCompress.Common;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using FellowOakDicom.IO.Buffer;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Service
|
||||
{
|
||||
|
|
@ -39,7 +41,9 @@ namespace IRaCIS.Core.SCP.Service
|
|||
{
|
||||
private IServiceProvider _serviceProvider { get; set; }
|
||||
|
||||
private List<Guid> _SCPStudyIdList { get; set; } = new List<Guid>();
|
||||
private List<Guid> _SCPStudyIdList => _ImageUploadList.Where(t => t.SCPStudyId != Guid.Empty).Select(t => t.SCPStudyId).ToList();
|
||||
|
||||
private List<ImageUploadInfo> _ImageUploadList { get; set; } = new List<ImageUploadInfo>();
|
||||
|
||||
private SCPImageUpload _upload { get; set; }
|
||||
|
||||
|
|
@ -76,9 +80,10 @@ namespace IRaCIS.Core.SCP.Service
|
|||
};
|
||||
|
||||
|
||||
public CStoreSCPService(INetworkStream stream, Encoding fallbackEncoding, Microsoft.Extensions.Logging.ILogger log, DicomServiceDependencies dependencies)
|
||||
public CStoreSCPService(INetworkStream stream, Encoding fallbackEncoding, Microsoft.Extensions.Logging.ILogger log, DicomServiceDependencies dependencies, IServiceProvider injectServiceProvider)
|
||||
: base(stream, fallbackEncoding, log, dependencies)
|
||||
{
|
||||
_serviceProvider = injectServiceProvider.CreateScope().ServiceProvider;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -92,9 +97,7 @@ namespace IRaCIS.Core.SCP.Service
|
|||
|
||||
Log.Logger.Warning($"接收到来自{association.CallingAE}的连接");
|
||||
|
||||
_serviceProvider = (IServiceProvider)this.UserState;
|
||||
|
||||
|
||||
//_serviceProvider = (IServiceProvider)this.UserState;
|
||||
|
||||
var _trialDicomAERepository = _serviceProvider.GetService<IRepository<TrialDicomAE>>();
|
||||
|
||||
|
|
@ -163,18 +166,19 @@ namespace IRaCIS.Core.SCP.Service
|
|||
|
||||
public async Task OnReceiveAssociationReleaseRequestAsync()
|
||||
{
|
||||
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
|
||||
|
||||
var @lock = _distributedLockProvider.CreateLock($"{_upload.CallingAE}");
|
||||
|
||||
using (await @lock.AcquireAsync())
|
||||
{
|
||||
|
||||
await DataMaintenanceAsaync();
|
||||
|
||||
//记录监控
|
||||
|
||||
var _SCPImageUploadRepository = _serviceProvider.GetService<IRepository<SCPImageUpload>>();
|
||||
await AddUploadLogAsync();
|
||||
|
||||
_upload.EndTime = DateTime.Now;
|
||||
_upload.StudyCount = _SCPStudyIdList.Count;
|
||||
_upload.TrialId = _trialId;
|
||||
_upload.TrialSiteId = _trialSiteId;
|
||||
|
||||
await _SCPImageUploadRepository.AddAsync(_upload, true);
|
||||
|
||||
|
||||
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
|
||||
|
|
@ -183,7 +187,27 @@ namespace IRaCIS.Core.SCP.Service
|
|||
|
||||
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
|
||||
|
||||
}
|
||||
|
||||
await SendAssociationReleaseResponseAsync();
|
||||
|
||||
}
|
||||
|
||||
private async Task AddUploadLogAsync()
|
||||
{
|
||||
//记录监控
|
||||
|
||||
var _SCPImageUploadRepository = _serviceProvider.GetService<IRepository<SCPImageUpload>>();
|
||||
|
||||
_upload.EndTime = DateTime.Now;
|
||||
_upload.StudyCount = _ImageUploadList.Count;
|
||||
_upload.TrialId = _trialId;
|
||||
_upload.TrialSiteId = _trialSiteId;
|
||||
|
||||
_upload.UploadJsonStr = (new SCPImageLog() { UploadList = _ImageUploadList }).ToJsonStr();
|
||||
|
||||
//可能是测试echo 导致记录了
|
||||
await _SCPImageUploadRepository.AddAsync(_upload, _upload.FileCount > 0 ? true : false);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -249,6 +273,11 @@ namespace IRaCIS.Core.SCP.Service
|
|||
|
||||
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
//记录日志
|
||||
await AddUploadLogAsync();
|
||||
}
|
||||
|
||||
Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}");
|
||||
}
|
||||
|
|
@ -259,9 +288,23 @@ namespace IRaCIS.Core.SCP.Service
|
|||
public async Task<DicomCStoreResponse> OnCStoreRequestAsync(DicomCStoreRequest request)
|
||||
{
|
||||
|
||||
string studyInstanceUid = request.Dataset.GetString(DicomTag.StudyInstanceUID);
|
||||
string seriesInstanceUid = request.Dataset.GetString(DicomTag.SeriesInstanceUID);
|
||||
string sopInstanceUid = request.Dataset.GetString(DicomTag.SOPInstanceUID);
|
||||
string studyInstanceUid = request.Dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty);
|
||||
string seriesInstanceUid = request.Dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty);
|
||||
string sopInstanceUid = request.Dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty);
|
||||
string patientIdStr = request.Dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
|
||||
|
||||
if (studyInstanceUid.IsNullOrEmpty() || seriesInstanceUid.IsNullOrEmpty() || sopInstanceUid.IsNullOrEmpty())
|
||||
{
|
||||
Log.Logger.Error($"接收数据读取StudyInstanceUID:{studyInstanceUid}、SeriesInstanceUID:{seriesInstanceUid}、SOPInstanceUID:{sopInstanceUid}有空 ");
|
||||
|
||||
return new DicomCStoreResponse(request, DicomStatus.Success);
|
||||
}
|
||||
|
||||
//确保来了影像集合存在
|
||||
if (!_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
|
||||
{
|
||||
_ImageUploadList.Add(new ImageUploadInfo() { StudyInstanceUid = studyInstanceUid });
|
||||
}
|
||||
|
||||
//Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
|
||||
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, _trialId.ToString());
|
||||
|
|
@ -281,11 +324,70 @@ namespace IRaCIS.Core.SCP.Service
|
|||
long fileSize = 0;
|
||||
try
|
||||
{
|
||||
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
await request.File.SaveAsync(ms);
|
||||
|
||||
#region 1帧拆成多个固定大小的,方便移动端浏览
|
||||
|
||||
// 回到开头,读取 dicom
|
||||
ms.Position = 0;
|
||||
var dicomFile = DicomFile.Open(ms);
|
||||
|
||||
var pixelData = DicomPixelData.Create(dicomFile.Dataset);
|
||||
var syntax = pixelData.Syntax;
|
||||
|
||||
// 每个 fragment 固定大小 (64KB 示例,可以自己调整)
|
||||
int fragmentSize = 20 * 1024;
|
||||
|
||||
if (syntax.IsEncapsulated)
|
||||
{
|
||||
var newFragments = new DicomOtherByteFragment(DicomTag.PixelData);
|
||||
|
||||
for (int n = 0; n < pixelData.NumberOfFrames; n++)
|
||||
{
|
||||
var frameData = pixelData.GetFrame(n); // 获取完整一帧
|
||||
var data = frameData.Data;
|
||||
int offset = 0;
|
||||
|
||||
while (offset < data.Length)
|
||||
{
|
||||
int size = Math.Min(fragmentSize, data.Length - offset);
|
||||
var buffer = new byte[size];
|
||||
Buffer.BlockCopy(data, offset, buffer, 0, size);
|
||||
|
||||
newFragments.Fragments.Add(new MemoryByteBuffer(buffer));
|
||||
offset += size;
|
||||
}
|
||||
}
|
||||
|
||||
// 替换原 PixelData
|
||||
dicomFile.Dataset.AddOrUpdate(newFragments);
|
||||
|
||||
|
||||
// 重新保存 dicom 到流
|
||||
ms.SetLength(0);
|
||||
dicomFile.Save(ms);
|
||||
}
|
||||
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 本地测试
|
||||
//// --- 保存到本地文件测试 ---
|
||||
//var localPath = @"D:\TestDicom.dcm";
|
||||
//using (var fs = new FileStream(localPath, FileMode.Create, FileAccess.Write))
|
||||
//{
|
||||
// ms.CopyTo(fs);
|
||||
//}
|
||||
//return new DicomCStoreResponse(request, DicomStatus.Success);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
//irc 从路径最后一截取Guid
|
||||
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false);
|
||||
|
||||
|
|
@ -308,12 +410,8 @@ namespace IRaCIS.Core.SCP.Service
|
|||
{
|
||||
try
|
||||
{
|
||||
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.Dataset, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize);
|
||||
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.File, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize);
|
||||
|
||||
if (!_SCPStudyIdList.Contains(scpStudyId))
|
||||
{
|
||||
_SCPStudyIdList.Add(scpStudyId);
|
||||
}
|
||||
|
||||
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
|
||||
|
||||
|
|
@ -342,12 +440,40 @@ namespace IRaCIS.Core.SCP.Service
|
|||
|
||||
|
||||
await _seriesRepository.SaveChangesAsync();
|
||||
|
||||
if (_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
|
||||
{
|
||||
var find = _ImageUploadList.FirstOrDefault(t => t.StudyInstanceUid.Equals(studyInstanceUid));
|
||||
|
||||
|
||||
find.SuccessImageCount++;
|
||||
|
||||
if (!find.PatientNameList.Any(t => t == patientIdStr) && patientIdStr.IsNotNullOrEmpty())
|
||||
{
|
||||
find.PatientNameList.Add(patientIdStr);
|
||||
}
|
||||
|
||||
//首次 (默认是Guid 空,数据库归档出了Id)
|
||||
if (find.SCPStudyId != scpStudyId)
|
||||
{
|
||||
find.SCPStudyId = scpStudyId;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
|
||||
|
||||
if (_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
|
||||
{
|
||||
var find = _ImageUploadList.FirstOrDefault(t => t.StudyInstanceUid.Equals(studyInstanceUid));
|
||||
|
||||
find.FailedImageCount++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using IRaCIS.Core.Infra.EFCore;
|
|||
using MassTransit;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using Serilog.Sinks.File;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Service
|
||||
{
|
||||
|
|
@ -52,8 +53,10 @@ namespace IRaCIS.Core.SCP.Service
|
|||
/// <param name="dataset"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public async Task<Guid> ArchiveDicomFileAsync(DicomDataset dataset, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE,long fileSize)
|
||||
public async Task<Guid> ArchiveDicomFileAsync(DicomFile dicomFile, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE,long fileSize)
|
||||
{
|
||||
var dataset = dicomFile.Dataset;
|
||||
|
||||
string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
|
||||
string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID);
|
||||
string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID);
|
||||
|
|
@ -82,7 +85,7 @@ namespace IRaCIS.Core.SCP.Service
|
|||
DateTime? studyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.StudyDate).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.StudyTime).TimeOfDay);
|
||||
|
||||
//先传输了修改了患者编号的,又传输了没有修改患者编号的,导致后传输的没有修改患者编号的下面的检查为0
|
||||
if (findPatient == null && findStudy==null)
|
||||
if (findPatient == null /*&& findStudy==null*/)
|
||||
{
|
||||
isPatientNeedAdd = true;
|
||||
|
||||
|
|
@ -151,6 +154,32 @@ namespace IRaCIS.Core.SCP.Service
|
|||
}
|
||||
|
||||
findPatient.LatestPushTime = DateTime.Now;
|
||||
findPatient.PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
|
||||
findPatient.PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty);
|
||||
findPatient.PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty);
|
||||
findPatient.PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty);
|
||||
|
||||
if (findPatient.PatientBirthDate.Length == 8)
|
||||
{
|
||||
var birthDateStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}-{findPatient.PatientBirthDate[4]}{findPatient.PatientBirthDate[5]}-{findPatient.PatientBirthDate[6]}{findPatient.PatientBirthDate[7]}";
|
||||
|
||||
var yearStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}";
|
||||
|
||||
int year = 0;
|
||||
|
||||
var canParse = int.TryParse(yearStr, out year);
|
||||
|
||||
if (canParse && year > 1900)
|
||||
{
|
||||
findPatient.PatientBirthDate = birthDateStr;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
findPatient.PatientBirthDate = string.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (findStudy == null)
|
||||
|
|
@ -167,6 +196,9 @@ namespace IRaCIS.Core.SCP.Service
|
|||
TrialSiteId = trialSiteId,
|
||||
StudyInstanceUid = studyInstanceUid,
|
||||
StudyTime = studyTime,
|
||||
DicomStudyDate = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty),
|
||||
DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty),
|
||||
|
||||
Modalities = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
|
||||
//ModalityForEdit = modalityForEdit,
|
||||
Description = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty),
|
||||
|
|
@ -188,6 +220,12 @@ namespace IRaCIS.Core.SCP.Service
|
|||
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
|
||||
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
|
||||
|
||||
Manufacturer = dataset.GetSingleValueOrDefault(DicomTag.Manufacturer, string.Empty),
|
||||
ManufacturerModelName = dataset.GetSingleValueOrDefault(DicomTag.ManufacturerModelName, string.Empty),
|
||||
DeviceSerialNumber = dataset.GetSingleValueOrDefault(DicomTag.DeviceSerialNumber, string.Empty),
|
||||
DeviceUID = dataset.GetSingleValueOrDefault(DicomTag.DeviceUID, string.Empty),
|
||||
SoftwareVersions = dataset.GetSingleValueOrDefault(DicomTag.SoftwareVersions, string.Empty),
|
||||
PatientWeight = dataset.GetSingleValueOrDefault(DicomTag.PatientWeight, string.Empty),
|
||||
|
||||
|
||||
//IsDoubleReview = addtionalInfo.IsDoubleReview,
|
||||
|
|
@ -201,6 +239,19 @@ namespace IRaCIS.Core.SCP.Service
|
|||
findStudy.PatientBirthDate = $"{findStudy.PatientBirthDate[0]}{findStudy.PatientBirthDate[1]}{findStudy.PatientBirthDate[2]}{findStudy.PatientBirthDate[3]}-{findStudy.PatientBirthDate[4]}{findStudy.PatientBirthDate[5]}-{findStudy.PatientBirthDate[6]}{findStudy.PatientBirthDate[7]}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
findStudy.DicomStudyDate = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty);
|
||||
findStudy.DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty);
|
||||
findStudy.CalledAE = calledAE;
|
||||
findStudy.CallingAE = callingAE;
|
||||
findStudy.PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
|
||||
findStudy.PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty);
|
||||
findStudy.PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty);
|
||||
findStudy.UpdateTime = DateTime.Now;
|
||||
|
||||
await _patientRepository.BatchUpdateNoTrackingAsync(t => t.Id == findStudy.PatientId, u => new SCPPatient() { LatestPushTime = DateTime.Now });
|
||||
}
|
||||
|
||||
|
||||
if (findSerice == null)
|
||||
|
|
@ -218,6 +269,9 @@ namespace IRaCIS.Core.SCP.Service
|
|||
//SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, DateTime.Now).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, DateTime.Now).TimeOfDay),
|
||||
//SeriesTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.SeriesDate) + dataset.GetSingleValue<string>(DicomTag.SeriesTime), out DateTime dt) ? dt : null,
|
||||
SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.SeriesDate).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.SeriesTime).TimeOfDay),
|
||||
DicomSeriesDate = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty),
|
||||
DicomSeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty),
|
||||
|
||||
Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
|
||||
Description = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty),
|
||||
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
|
||||
|
|
@ -233,13 +287,29 @@ namespace IRaCIS.Core.SCP.Service
|
|||
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
|
||||
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
|
||||
|
||||
RadiopharmaceuticalInformationSequence = dataset.GetSingleValueOrDefault(DicomTag.RadiopharmaceuticalInformationSequence, string.Empty),
|
||||
AcquisitionDate = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionDate, string.Empty),
|
||||
|
||||
|
||||
InstanceCount = 0
|
||||
};
|
||||
|
||||
++findStudy.SeriesCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
findSerice.DicomSeriesDate = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty);
|
||||
findSerice.DicomSeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty);
|
||||
findSerice.UpdateTime = DateTime.Now;
|
||||
}
|
||||
|
||||
var transferSyntaxUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
|
||||
|
||||
var isEncapsulated = false;
|
||||
if (transferSyntaxUID.IsNotNullOrEmpty())
|
||||
{
|
||||
isEncapsulated = DicomTransferSyntax.Lookup(DicomUID.Parse(transferSyntaxUID)).IsEncapsulated;
|
||||
}
|
||||
|
||||
if (findInstance == null)
|
||||
{
|
||||
|
|
@ -271,6 +341,21 @@ namespace IRaCIS.Core.SCP.Service
|
|||
WindowCenter = dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, string.Empty),
|
||||
WindowWidth = dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, string.Empty),
|
||||
|
||||
PhotometricInterpretation = dataset.GetSingleValueOrDefault(DicomTag.PhotometricInterpretation, string.Empty),
|
||||
BitsAllocated = dataset.GetSingleValueOrDefault(DicomTag.BitsAllocated, 0),
|
||||
PixelRepresentation = dataset.GetSingleValueOrDefault(DicomTag.PixelRepresentation, string.Empty),
|
||||
RescaleIntercept = dataset.GetSingleValueOrDefault(DicomTag.RescaleIntercept, string.Empty),
|
||||
RescaleSlope = dataset.GetSingleValueOrDefault(DicomTag.RescaleSlope, string.Empty),
|
||||
ImagePositionPatient = dataset.GetSingleValueOrDefault(DicomTag.ImagePositionPatient, string.Empty),
|
||||
ImageOrientationPatient = dataset.GetSingleValueOrDefault(DicomTag.ImageOrientationPatient, string.Empty),
|
||||
SequenceOfUltrasoundRegions = dataset.GetSingleValueOrDefault(DicomTag.SequenceOfUltrasoundRegions, string.Empty),
|
||||
FrameTime = dataset.GetSingleValueOrDefault(DicomTag.FrameTime, string.Empty),
|
||||
CorrectedImage = dataset.GetSingleValueOrDefault(DicomTag.CorrectedImage, string.Empty),
|
||||
Units = dataset.GetSingleValueOrDefault(DicomTag.Units, string.Empty),
|
||||
DecayCorrection = dataset.GetSingleValueOrDefault(DicomTag.DecayCorrection, string.Empty),
|
||||
EncapsulatedDocument = dataset.GetSingleValueOrDefault(DicomTag.EncapsulatedDocument, string.Empty),
|
||||
|
||||
|
||||
Path = fileRelativePath,
|
||||
|
||||
FileSize= fileSize,
|
||||
|
|
@ -280,6 +365,15 @@ namespace IRaCIS.Core.SCP.Service
|
|||
++findStudy.InstanceCount;
|
||||
++findSerice.InstanceCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
findInstance.SOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty);
|
||||
findInstance.MediaStorageSOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty);
|
||||
findInstance.TransferSytaxUID = transferSyntaxUID;
|
||||
findInstance.MediaStorageSOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty);
|
||||
findInstance.IsEncapsulated = isEncapsulated;
|
||||
findInstance.UpdateTime = DateTime.Now;
|
||||
}
|
||||
|
||||
if (isPatientNeedAdd)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace IRaCIS.Core.SCP.Service
|
|||
{
|
||||
public interface IDicomArchiveService
|
||||
{
|
||||
Task<Guid> ArchiveDicomFileAsync(DicomDataset dicomDataset,Guid trialId,Guid trialSiteId, string fileRelativePath,string callingAE,string calledAE,long fileSize);
|
||||
Task<Guid> ArchiveDicomFileAsync(DicomFile dicomFile, Guid trialId,Guid trialSiteId, string fileRelativePath,string callingAE,string calledAE,long fileSize);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRaCIS.Core.Infrastructure"
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRC.Core.SCP", "IRC.Core.SCP\IRC.Core.SCP.csproj", "{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IRC.Core.Dicom", "IRC.Core.Dicom\IRC.Core.Dicom.csproj", "{0545F0A5-D97B-4A47-92A6-A8A02A181322}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -57,6 +59,10 @@ Global
|
|||
{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Amazon.Auth.AccessControlPolicy;
|
||||
using AlibabaCloud.SDK.Sts20150401;
|
||||
using Amazon.Auth.AccessControlPolicy;
|
||||
using Amazon.SecurityToken;
|
||||
using AutoMapper;
|
||||
using Azure.Core;
|
||||
|
|
@ -21,10 +22,12 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Org.BouncyCastle.Tls;
|
||||
using RestSharp;
|
||||
using RestSharp.Authenticators;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
|
@ -123,7 +126,7 @@ namespace IRaCIS.Api.Controllers
|
|||
var token = _tokenService.GetToken(new UserTokenInfo()
|
||||
{
|
||||
IdentityUserId = Guid.NewGuid(),
|
||||
UserName = "Share001",
|
||||
UserName = "ImageShare",
|
||||
UserTypeEnum = UserTypeEnum.ShareImage,
|
||||
|
||||
});
|
||||
|
|
@ -181,11 +184,74 @@ namespace IRaCIS.Api.Controllers
|
|||
}
|
||||
|
||||
|
||||
#region 老项目依赖
|
||||
|
||||
[HttpGet("user/GenerateSTS")]
|
||||
public IResponseOutput GenerateSTS([FromServices] IOptionsMonitor<ObjectStoreServiceOptions> options)
|
||||
{
|
||||
|
||||
var ossOptions = options.CurrentValue.AliyunOSS;
|
||||
|
||||
var client = new Client(new AlibabaCloud.OpenApiClient.Models.Config()
|
||||
{
|
||||
AccessKeyId = ossOptions.AccessKeyId,
|
||||
AccessKeySecret = ossOptions.AccessKeySecret,
|
||||
Endpoint = "sts.cn-hangzhou.aliyuncs.com"
|
||||
});
|
||||
|
||||
var assumeRoleRequest = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest();
|
||||
// 将<YOUR_ROLE_SESSION_NAME>设置为自定义的会话名称,例如oss-role-session。
|
||||
assumeRoleRequest.RoleSessionName = $"session-name-{NewId.NextGuid()}";
|
||||
// 将<YOUR_ROLE_ARN>替换为拥有上传文件到指定OSS Bucket权限的RAM角色的ARN。
|
||||
assumeRoleRequest.RoleArn = ossOptions.RoleArn;
|
||||
//assumeRoleRequest.RoleArn = "acs:ram::1899121822495495:role/webdirect";
|
||||
assumeRoleRequest.DurationSeconds = ossOptions.DurationSeconds;
|
||||
var runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions();
|
||||
var response = client.AssumeRoleWithOptions(assumeRoleRequest, runtime);
|
||||
var credentials = response.Body.Credentials;
|
||||
|
||||
var tempToken = new AliyunOSSTempToken()
|
||||
{
|
||||
AccessKeyId = credentials.AccessKeyId,
|
||||
AccessKeySecret = credentials.AccessKeySecret,
|
||||
|
||||
//转为服务器时区,最后统一转为客户端时区
|
||||
Expiration = TimeZoneInfo.ConvertTimeFromUtc(DateTime.Parse(credentials.Expiration), TimeZoneInfo.Local),
|
||||
SecurityToken = credentials.SecurityToken,
|
||||
|
||||
|
||||
Region = ossOptions.Region,
|
||||
BucketName = ossOptions.BucketName,
|
||||
EndPoint = ossOptions.EndPoint,
|
||||
ViewEndpoint = ossOptions.ViewEndpoint,
|
||||
PreviewEndpoint = ossOptions.PreviewEndpoint
|
||||
|
||||
};
|
||||
|
||||
// 返回STS令牌信息给前端
|
||||
var stsToken = new
|
||||
{
|
||||
AccessKeyId = credentials.AccessKeyId,
|
||||
AccessKeySecret = credentials.AccessKeySecret,
|
||||
SecurityToken = credentials.SecurityToken,
|
||||
Expiration = credentials.Expiration,
|
||||
|
||||
Region = ossOptions.Region,
|
||||
BucketName = ossOptions.BucketName,
|
||||
ViewEndpoint = ossOptions.ViewEndpoint,
|
||||
|
||||
};
|
||||
|
||||
return ResponseOutput.Ok(stsToken);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
[HttpGet("User/UserRedirect")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> UserRedirect([FromServices] IRepository<IdentityUser> _useRepository, string url, [FromServices] ILogger<ExtraController> _logger)
|
||||
public async Task<IActionResult> UserRedirect([FromServices] IRepository<IdentityUser> _useRepository, string url, [FromServices] ILogger<ExtraController> _logger, [FromServices] ITokenService _tokenService)
|
||||
{
|
||||
|
||||
var decodeUrl = System.Web.HttpUtility.UrlDecode(url);
|
||||
|
|
@ -200,10 +266,22 @@ namespace IRaCIS.Api.Controllers
|
|||
|
||||
var errorUrl = domainStrList[0] + "//" + domainStrList[2] + "/error";
|
||||
|
||||
|
||||
if (!await _useRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd))
|
||||
if (lang == "zh")
|
||||
{
|
||||
decodeUrl = errorUrl + $"?lang={lang}&ErrorMessage={System.Web.HttpUtility.UrlEncode(lang == "zh" ? "您的初始化链接已过期" : "Error!The initialization link has expired.")} ";
|
||||
CultureInfo.CurrentCulture = new CultureInfo(StaticData.CultureInfo.zh_CN);
|
||||
CultureInfo.CurrentUICulture = new CultureInfo(StaticData.CultureInfo.zh_CN);
|
||||
}
|
||||
else
|
||||
{
|
||||
CultureInfo.CurrentCulture = new CultureInfo(StaticData.CultureInfo.en_US);
|
||||
CultureInfo.CurrentUICulture = new CultureInfo(StaticData.CultureInfo.en_US);
|
||||
}
|
||||
|
||||
var isExpire = _tokenService.IsTokenExpired(token);
|
||||
|
||||
if (!await _useRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd) || isExpire)
|
||||
{
|
||||
decodeUrl = errorUrl + $"?lang={lang}&ErrorMessage={System.Web.HttpUtility.UrlEncode(I18n.T("UserRedirect_InitializationLinkExpire"))} ";
|
||||
}
|
||||
|
||||
return Redirect(decodeUrl);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Contracts.DTO;
|
||||
using IRaCIS.Core.Application.Filter;
|
||||
using IRaCIS.Core.Application.Image.QA;
|
||||
using IRaCIS.Core.Application.Service;
|
||||
|
|
@ -34,6 +35,31 @@ namespace IRaCIS.Core.API.Controllers
|
|||
) : ControllerBase
|
||||
{
|
||||
|
||||
|
||||
[HttpPost, Route("Inspection/NoneDicomStudy/UpdateNoneDicomStudy")]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
[UnitOfWork]
|
||||
|
||||
public async Task<IResponseOutput> UpdateNoneDicomStudy(DataInspectionDto<NoneDicomEdit> opt, [FromServices] INoneDicomStudyService _noneDicomStudyService)
|
||||
{
|
||||
var singId = await _inspectionService.RecordSing(opt.SignInfo);
|
||||
var result = await _noneDicomStudyService.UpdateNoneDicomStudy(opt.Data);
|
||||
await _inspectionService.CompletedSign(singId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[HttpPost, Route("Inspection/QCOperation/UpdateDicomStudyInfo")]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
[UnitOfWork]
|
||||
|
||||
public async Task<IResponseOutput> UpdateDicomStudyInfo(DataInspectionDto<DicomStudyEdit> opt, [FromServices] IQCOperationService _qcOperationService)
|
||||
{
|
||||
var singId = await _inspectionService.RecordSing(opt.SignInfo);
|
||||
var result = await _qcOperationService.UpdateDicomStudyInfo(opt.Data);
|
||||
await _inspectionService.CompletedSign(singId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
#region 获取稽查数据
|
||||
/// <summary>
|
||||
/// 获取稽查数据
|
||||
|
|
@ -52,7 +78,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingImageTask/SubmitOncologyReadingInfo")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SetOncologyReadingInfo(DataInspectionDto<SubmitOncologyReadingInfoInDto> opt)
|
||||
|
|
@ -69,7 +95,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingImageTask/SubmitDicomVisitTask")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SubmitDicomVisitTask(DataInspectionDto<SubmitDicomVisitTaskInDto> opt)
|
||||
|
|
@ -88,7 +114,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingImageTask/SubmitGlobalReadingInfo")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SubmitGlobalReadingInfo(DataInspectionDto<SubmitGlobalReadingInfoInDto> opt)
|
||||
|
|
@ -107,7 +133,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/configTrialBasicInfo/TrialReadingInfoSign")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> TrialReadingInfoSign(DataInspectionDto<TrialReadingInfoSignInDto> opt)
|
||||
|
|
@ -126,7 +152,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingMedicalReview/FinishMedicalReview")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> FinishMedicalReview(DataInspectionDto<FinishMedicalReviewInDto> opt)
|
||||
|
|
@ -143,7 +169,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingMedicineQuestion/ConfirmReadingMedicineQuestion")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> ConfirmReadingMedicineQuestion(DataInspectionDto<ConfirmReadingMedicineQuestionInDto> opt)
|
||||
|
|
@ -161,7 +187,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingImageTask/SubmitVisitTaskQuestions")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SubmitVisitTaskQuestions(DataInspectionDto<SubmitVisitTaskQuestionsInDto> opt)
|
||||
|
|
@ -179,7 +205,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ClinicalAnswer/CRCSignClinicalData")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> CRCSignClinicalData(DataInspectionDto<CRCSignClinicalDataInDto> opt)
|
||||
|
|
@ -197,7 +223,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ClinicalAnswer/CRCConfirmClinical")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> CRCConfirmClinical(DataInspectionDto<CRCConfirmClinicalInDto> opt)
|
||||
|
|
@ -214,7 +240,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ClinicalAnswer/CRCCancelConfirmClinical")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> CRCCancelConfirmClinical(DataInspectionDto<CRCCancelConfirmClinicalInDto> opt)
|
||||
|
|
@ -232,7 +258,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ClinicalAnswer/PMConfirmClinical")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> PMConfirmClinical(DataInspectionDto<CRCConfirmClinicalInDto> opt)
|
||||
|
|
@ -250,7 +276,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingClinicalData/SignConsistencyAnalysisReadingClinicalData")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SignConsistencyAnalysisReadingClinicalData(DataInspectionDto<SignConsistencyAnalysisReadingClinicalDataInDto> opt)
|
||||
|
|
@ -267,7 +293,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ClinicalAnswer/SubmitClinicalFormAndSign")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SubmitClinicalFormAndSign(DataInspectionDto<SubmitClinicalFormInDto> opt)
|
||||
|
|
@ -284,7 +310,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingImageTask/SubmitJudgeVisitTaskResult")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SubmitJudgeVisitTaskResult(DataInspectionDto<SaveJudgeVisitTaskResult> opt)
|
||||
|
|
@ -303,7 +329,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialBasicInfoConfirm")]
|
||||
[UnitOfWork]
|
||||
[TrialGlobalLimit( "BeforeOngoingCantOpt" )]
|
||||
[TrialGlobalLimit("BeforeOngoingCantOpt")]
|
||||
public async Task<IResponseOutput> ConfigTrialBasicInfoConfirm(DataInspectionDto<BasicTrialConfig> opt)
|
||||
{
|
||||
|
||||
|
|
@ -345,7 +371,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialUrgentInfoConfirm")]
|
||||
[UnitOfWork]
|
||||
[TrialGlobalLimit( "BeforeOngoingCantOpt" )]
|
||||
[TrialGlobalLimit("BeforeOngoingCantOpt")]
|
||||
public async Task<IResponseOutput> ConfigTrialUrgentInfoConfirm(DataInspectionDto<TrialUrgentConfig> opt)
|
||||
{
|
||||
opt.Data.IsTrialUrgentConfirmed = true;
|
||||
|
|
@ -358,7 +384,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialPACSInfoConfirm")]
|
||||
[UnitOfWork]
|
||||
[TrialGlobalLimit( "BeforeOngoingCantOpt" )]
|
||||
[TrialGlobalLimit("BeforeOngoingCantOpt")]
|
||||
public async Task<IResponseOutput> ConfigTrialPACSInfoConfirm(DataInspectionDto<TrialPACSConfig> opt)
|
||||
{
|
||||
opt.Data.IsTrialPACSConfirmed = true;
|
||||
|
|
@ -374,7 +400,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/configTrialBasicInfo/TrialConfigSignatureConfirm")]
|
||||
[UnitOfWork]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
public async Task<IResponseOutput> TrialConfigSignatureConfirm(DataInspectionDto<SignConfirmDTO> opt)
|
||||
{
|
||||
|
|
@ -391,7 +417,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingCriterion/ResetAndAsyncCriterion")]
|
||||
[UnitOfWork]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
public async Task<IResponseOutput> ResetAndAsyncCriterion(DataInspectionDto<ResetAndAsyncCriterionInDto> opt)
|
||||
{
|
||||
|
|
@ -409,7 +435,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/QCOperation/CRCRequestToQC")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> CRCRequestToQC(DataInspectionDto<CRCRequestToQCCommand> opt)
|
||||
{
|
||||
|
|
@ -424,21 +450,31 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// 设置QC 通过或者不通过 7:QC failed 8:QC passed
|
||||
/// </summary>
|
||||
[HttpPost, Route("Inspection/QCOperation/QCPassedOrFailed")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> QCPassedOrFailed(DataInspectionDto<QCPassedOrFailedDto> opt)
|
||||
{
|
||||
var singid = await _inspectionService.RecordSing(opt.SignInfo);
|
||||
if (opt.Data.IsSecondPass != null)
|
||||
{
|
||||
var result = await _qCOperationService.QCSecondReviewPassedOrFailed(opt.Data.trialId, opt.Data.subjectVisitId, (bool)opt.Data.IsSecondPass);
|
||||
await _inspectionService.CompletedSign(singid, result);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await _qCOperationService.QCPassedOrFailed(opt.Data.trialId, opt.Data.subjectVisitId, opt.Data.auditState);
|
||||
await _inspectionService.CompletedSign(singid, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一致性核查 回退 对话记录不清除 只允许PM回退
|
||||
/// </summary>
|
||||
[HttpPost, Route("Inspection/QCOperation/CheckBack")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> CheckBack(DataInspectionDto<IDDto> opt)
|
||||
{
|
||||
|
|
@ -455,7 +491,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadClinicalData/ReadClinicalDataSign")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> ReadClinicalDataSign(DataInspectionDto<ReadingClinicalDataSignIndto> opt)
|
||||
{
|
||||
|
|
@ -470,7 +506,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// CRC 设置已经重传完成
|
||||
/// </summary>
|
||||
[HttpPost, Route("Inspection/QCOperation/SetReuploadFinished")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SetReuploadFinished(DataInspectionDto<CRCReuploadFinishedCommand> opt)
|
||||
{
|
||||
|
|
@ -486,7 +522,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/TrialConfig/updateTrialState")]
|
||||
[TrialGlobalLimit( "BeforeOngoingCantOpt")]
|
||||
[TrialGlobalLimit("BeforeOngoingCantOpt")]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> UpdateTrialState(DataInspectionDto<UpdateTrialStateDto> opt)
|
||||
{
|
||||
|
|
@ -502,7 +538,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/TrialDocument/userConfirm")]
|
||||
[TrialGlobalLimit( "BeforeOngoingCantOpt", "SignSystemDocNoTrialId", "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("BeforeOngoingCantOpt", "SignSystemDocNoTrialId", "AfterStopCannNotOpt")]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> UserConfirm(DataInspectionDto<UserConfirmCommand> opt)
|
||||
{
|
||||
|
|
@ -519,7 +555,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/VisitTask/ConfirmReReading")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
[UnitOfWork]
|
||||
|
||||
public async Task<IResponseOutput> ConfirmReReading(DataInspectionDto<ConfirmReReadingCommand> opt, [FromServices] IVisitTaskService _visitTaskService)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ using Microsoft.AspNetCore.WebUtilities;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using MiniExcelLibs;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -38,6 +39,7 @@ using System.Data;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
|
|
@ -271,6 +273,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
public List<OSSFileDTO> UploadedFileList { get; set; } = new List<OSSFileDTO>();
|
||||
|
||||
public bool? IsImageSegmentLabel { get; set; }
|
||||
|
||||
public class OSSFileDTO
|
||||
{
|
||||
|
|
@ -516,6 +519,13 @@ namespace IRaCIS.Core.API.Controllers
|
|||
{
|
||||
await _noneDicomStudyFileRepository.AddAsync(new NoneDicomStudyFile() { FileName = item.FileName, Path = item.FilePath, OriginNoneDicomStudyId = noneDicomStudyId.Value, VisitTaskId = incommand.VisitTaskId, FileType = item.FileType, FileSize = item.FileFize });
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (incommand.IsImageSegmentLabel == true)
|
||||
{
|
||||
await _noneDicomStudyFileRepository.AddAsync(new NoneDicomStudyFile() { FileName = item.FileName, Path = item.FilePath, ImageLabelNoneDicomStudyId = noneDicomStudyId.Value, FileType = item.FileType, FileSize = item.FileFize });
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -525,9 +535,12 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
var uploadFinishedTime = DateTime.Now;
|
||||
|
||||
var noneDicomStudy = await _noneDicomStudyRepository.FirstOrDefaultAsync((t => t.Id == noneDicomStudyId));
|
||||
var noneDicomStudy = await _noneDicomStudyRepository.FirstOrDefaultAsync(t => t.Id == noneDicomStudyId, true);
|
||||
|
||||
noneDicomStudy.FileCount = noneDicomStudy.FileCount + (incommand.VisitTaskId != null ? 0 : incommand.UploadedFileList.Count);
|
||||
|
||||
|
|
@ -555,6 +568,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// 一致性核查 excel上传 支持三种格式
|
||||
/// </summary>
|
||||
/// <param name="trialId"></param>
|
||||
/// <param name="isFullCheck"></param>
|
||||
/// <param name="oSSService"></param>
|
||||
/// <param name="_inspectionFileRepository"></param>
|
||||
/// <returns></returns>
|
||||
|
|
@ -562,12 +576,14 @@ namespace IRaCIS.Core.API.Controllers
|
|||
[HttpPost("QCOperation/UploadVisitCheckExcel/{trialId:guid}")]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
public async Task<IResponseOutput> UploadVisitCheckExcel(Guid trialId, [FromServices] IOSSService oSSService, [FromServices] IRepository<InspectionFile> _inspectionFileRepository)
|
||||
public async Task<IResponseOutput> UploadVisitCheckExcel(Guid trialId, bool isFullCheck, [FromServices] IOSSService oSSService, [FromServices] IRepository<InspectionFile> _inspectionFileRepository)
|
||||
{
|
||||
|
||||
var fileName = string.Empty;
|
||||
var templateFileStream = new MemoryStream();
|
||||
|
||||
var inspectionFileId = Guid.Empty;
|
||||
|
||||
await FileUploadToOSSAsync(async (realFileName, fileStream) =>
|
||||
{
|
||||
fileName = realFileName;
|
||||
|
|
@ -582,9 +598,11 @@ namespace IRaCIS.Core.API.Controllers
|
|||
templateFileStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
|
||||
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, "InspectionUpload/Check", realFileName);
|
||||
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", realFileName);
|
||||
|
||||
await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId });
|
||||
var addEntity = await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId }, true);
|
||||
|
||||
inspectionFileId = addEntity.Id;
|
||||
|
||||
return ossRelativePath;
|
||||
|
||||
|
|
@ -730,8 +748,11 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
if (etcCheckList == null || etcCheckList.Count == 0)
|
||||
{
|
||||
await _inspectionFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == inspectionFileId, u => new InspectionFile() { CheckState = EDCCheckState.Failed });
|
||||
|
||||
//---请保证上传数据符合模板文件中的样式,且存在有效数据。
|
||||
return ResponseOutput.NotOk(_localizer["UploadDownLoad_InvalidData"]);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -751,6 +772,8 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
if (etcCheckList.Count == 0)
|
||||
{
|
||||
await _inspectionFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == inspectionFileId, u => new InspectionFile() { CheckState = EDCCheckState.Failed });
|
||||
|
||||
//---请保证上传数据符合模板文件中的样式,且存在有效数据。
|
||||
return ResponseOutput.NotOk(_localizer["UploadDownLoad_InvalidData"]);
|
||||
}
|
||||
|
|
@ -762,8 +785,19 @@ namespace IRaCIS.Core.API.Controllers
|
|||
//var client = _mediator.CreateRequestClient<ConsistenCheckCommand>();
|
||||
//await client.GetResponse<ConsistenCheckResult>(new ConsistenCheckCommand() { ETCList = etcCheckList, TrialId = trialId });
|
||||
|
||||
if (isFullCheck)
|
||||
{
|
||||
await _mediator.Send(new ConsistenFullCheckCommand() { ETCList = etcCheckList, TrialId = trialId, InspectionFileId = inspectionFileId });
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
//不获取结果,不用定义返回类型
|
||||
await _mediator.Send(new ConsistenCheckCommand() { ETCList = etcCheckList, TrialId = trialId });
|
||||
}
|
||||
|
||||
|
||||
|
||||
return ResponseOutput.Ok();
|
||||
|
||||
|
|
@ -799,12 +833,13 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
[HttpPost, Route("TrialSiteSurvey/UploadTrialSiteSurveyUser")]
|
||||
[DisableFormValueModelBinding]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> UploadTrialSiteSurveyUser(Guid trialId, string baseUrl, string routeUrl,
|
||||
[FromServices] IRepository<TrialSite> _trialSiteRepository,
|
||||
[FromServices] IRepository<UserType> _usertypeRepository,
|
||||
[FromServices] ITrialSiteSurveyService _trialSiteSurveyService,
|
||||
[FromServices] IOSSService oSSService,
|
||||
[FromServices] IOptionsMonitor<SystemEmailSendConfig> _systemEmailConfig,
|
||||
|
||||
[FromServices] IRepository<InspectionFile> _inspectionFileRepository)
|
||||
{
|
||||
var templateFileStream = new MemoryStream();
|
||||
|
|
@ -821,9 +856,9 @@ namespace IRaCIS.Core.API.Controllers
|
|||
throw new BusinessValidationFailedException(_localizer["UploadDownLoad_TemplateUploadData"]);
|
||||
}
|
||||
|
||||
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, "InspectionUpload/SiteSurvey", realFileName);
|
||||
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/SiteSurvey", realFileName);
|
||||
|
||||
await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId });
|
||||
await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId }, true);
|
||||
|
||||
|
||||
|
||||
|
|
@ -836,6 +871,26 @@ namespace IRaCIS.Core.API.Controllers
|
|||
.Where(t => !(string.IsNullOrWhiteSpace(t.TrialSiteCode) && string.IsNullOrWhiteSpace(t.FirstName) && string.IsNullOrWhiteSpace(t.LastName) && string.IsNullOrWhiteSpace(t.Email)
|
||||
&& string.IsNullOrWhiteSpace(t.Phone) && string.IsNullOrWhiteSpace(t.UserTypeStr) && string.IsNullOrWhiteSpace(t.OrganizationName))).ToList();
|
||||
|
||||
//处理前后空格
|
||||
foreach (var excel in excelList)
|
||||
{
|
||||
excel.Email = excel.Email.Trim();
|
||||
excel.Phone = excel.Phone.Trim();
|
||||
excel.OrganizationName = excel.OrganizationName.Trim();
|
||||
excel.UserTypeStr = excel.UserTypeStr?.Trim();
|
||||
excel.TrialSiteCode = excel.TrialSiteCode.Trim();
|
||||
excel.FirstName = excel.FirstName.Trim();
|
||||
excel.LastName = excel.LastName.Trim();
|
||||
}
|
||||
|
||||
var emailRegexStr = _systemEmailConfig.CurrentValue.EmailRegexStr;
|
||||
if (excelList.Any(t => !Regex.IsMatch(t.Email, emailRegexStr)))
|
||||
{
|
||||
var errorList = excelList.Where(t => !Regex.IsMatch(t.Email, emailRegexStr)).Select(t => t.Email).ToList();
|
||||
//有邮箱不符合邮箱格式,请核查Excel数据
|
||||
throw new BusinessValidationFailedException(_localizer["UploadDownLoad_InvalidEmail"] + string.Join(" | ", errorList));
|
||||
}
|
||||
|
||||
if (excelList.Any(t => string.IsNullOrWhiteSpace(t.TrialSiteCode) || string.IsNullOrWhiteSpace(t.FirstName) || string.IsNullOrWhiteSpace(t.LastName) || string.IsNullOrWhiteSpace(t.Email) || string.IsNullOrWhiteSpace(t.UserTypeStr)))
|
||||
{
|
||||
//请确保Excel中 每一行的 中心编号,姓名,邮箱,用户类型数据记录完整再进行上传
|
||||
|
|
@ -864,11 +919,6 @@ namespace IRaCIS.Core.API.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
if (excelList.Any(t => !t.Email.Contains("@")))
|
||||
{
|
||||
//有邮箱不符合邮箱格式,请核查Excel数据
|
||||
throw new BusinessValidationFailedException(_localizer["UploadDownLoad_InvalidEmail"]);
|
||||
}
|
||||
var generateUserTypeList = new List<string>() { "CRC", "CRA" };
|
||||
|
||||
//if (excelList.Any(t => !generateUserTypeList.Contains(t.UserTypeStr.ToUpper())))
|
||||
|
|
@ -954,7 +1004,11 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
EmailBodyHtml = 4,
|
||||
|
||||
Other = 5
|
||||
ReadKeyFile = 5,
|
||||
|
||||
Other = 6,
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -986,6 +1040,9 @@ namespace IRaCIS.Core.API.Controllers
|
|||
case UploadFileType.EmailBodyHtml:
|
||||
result = await SingleFileUploadAsync((fileName) => FileStoreHelper.GetSystemFileUploadPath(_hostEnvironment, StaticData.Folder.EmailTemplate, fileName));
|
||||
break;
|
||||
case UploadFileType.ReadKeyFile:
|
||||
result = await SingleFileUploadAsync((fileName) => FileStoreHelper.GetSystemFileUploadPath(_hostEnvironment, StaticData.Folder.ReadKetFile, fileName));
|
||||
break;
|
||||
|
||||
default:
|
||||
result = await SingleFileUploadAsync((fileName) => FileStoreHelper.GetOtherFileUploadPath(_hostEnvironment, StaticData.Folder.TempFile, fileName));
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ namespace IRaCIS.Core.API.HostService;
|
|||
|
||||
public class HangfireHostService(IRecurringMessageScheduler _recurringMessageScheduler,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
|
||||
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IMediator _mediator,
|
||||
ILogger<HangfireHostService> _logger) : IHostedService
|
||||
{
|
||||
|
|
@ -39,7 +41,22 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
|
|||
HangfireJobHelper.RemoveCronJob(jobId);
|
||||
}
|
||||
|
||||
// 清除所有可能存在的定时任务,防止类型加载错误
|
||||
var allJobIdList = JobStorage.Current.GetConnection().GetRecurringJobs().Select(t => t.Id).ToList();
|
||||
foreach (var jobId in allJobIdList)
|
||||
{
|
||||
try
|
||||
{
|
||||
HangfireJobHelper.RemoveCronJob(jobId);
|
||||
_logger.LogInformation($"已清除定时任务: {jobId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"清除定时任务 {jobId} 时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// 项目手动选择,周期性邮件
|
||||
var taskInfoList = await _trialEmailNoticeConfigRepository.Where(t => t.Trial.TrialStatusStr == StaticData.TrialState.TrialOngoing && t.EmailCron != string.Empty && t.IsAutoSend)
|
||||
.Select(t => new { t.Id, t.Code, TrialCode = t.Trial.TrialCode, t.EmailCron, t.BusinessScenarioEnum, t.TrialId })
|
||||
.ToListAsync();
|
||||
|
|
@ -55,6 +72,20 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
|
|||
}
|
||||
|
||||
|
||||
// 系统邮件定时任务
|
||||
var systemTaskInfoList = await _emailNoticeConfigrepository.Where(t => t.EmailCron != string.Empty && t.IsAutoSend)
|
||||
.Select(t => new { t.Id, t.Code, t.EmailCron, t.BusinessScenarioEnum, })
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var task in systemTaskInfoList)
|
||||
{
|
||||
//利用主键作为任务Id
|
||||
var jobId = $"{task.Id}_({task.BusinessScenarioEnum})";
|
||||
|
||||
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron);
|
||||
}
|
||||
|
||||
|
||||
//await _recurringMessageScheduler.ScheduleRecurringPublish(new QCImageQuestionSchedule() { CronExpression = "0/3 * * * * ? " }, new MasstransiTestCommand { value = "message at " + DateTime.Now.ToString() });
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -69,24 +69,22 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.14" />
|
||||
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.15">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ConfigMapFileProvider" Version="2.0.1" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.14" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.20" />
|
||||
<PackageReference Include="Hangfire.Dashboard.BasicAuthorization" Version="1.0.2" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="1.0.0" />
|
||||
<PackageReference Include="Hangfire.SqlServer" Version="1.8.14" />
|
||||
<PackageReference Include="Invio.Extensions.Authentication.JwtBearer" Version="2.0.1" />
|
||||
<PackageReference Include="LogDashboard" Version="1.4.8" />
|
||||
<PackageReference Include="Hangfire.SqlServer" Version="1.8.20" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Email" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.9.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Email" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -114,6 +112,12 @@
|
|||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Resources\GeoLite2-City.mmdb">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties properties_4launchsettings_1json__JsonSchema="" />
|
||||
|
|
|
|||
|
|
@ -318,11 +318,12 @@
|
|||
<param name="_noneDicomStudyFileRepository"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Core.API.Controllers.StudyController.UploadVisitCheckExcel(System.Guid,IRaCIS.Core.Application.Helper.IOSSService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.InspectionFile})">
|
||||
<member name="M:IRaCIS.Core.API.Controllers.StudyController.UploadVisitCheckExcel(System.Guid,System.Boolean,IRaCIS.Core.Application.Helper.IOSSService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.InspectionFile})">
|
||||
<summary>
|
||||
一致性核查 excel上传 支持三种格式
|
||||
</summary>
|
||||
<param name="trialId"></param>
|
||||
<param name="isFullCheck"></param>
|
||||
<param name="oSSService"></param>
|
||||
<param name="_inspectionFileRepository"></param>
|
||||
<returns></returns>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
using IRaCIS.Core.API;
|
||||
using IRaCIS.Core.API.HostService;
|
||||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using IRaCIS.Core.Application.BusinessFilter.LegacyController.Database.Api;
|
||||
using IRaCIS.Core.Application.Filter;
|
||||
using IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
using IRaCIS.Core.Application.Service;
|
||||
using IRaCIS.Core.Application.Service.BusinessFilter;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using LogDashboard;
|
||||
using MassTransit;
|
||||
using MassTransit.NewIdProviders;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
|
@ -37,6 +37,11 @@ var config = new ConfigurationBuilder()
|
|||
|
||||
var enviromentName = config["ASPNETCORE_ENVIRONMENT"];
|
||||
|
||||
var openSwaggerStr = config["ASPNETCORE_OpenSwagger"];
|
||||
|
||||
var isOpenSwagger= openSwaggerStr == null|| openSwaggerStr?.ToLower()=="true";
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(enviromentName))
|
||||
{
|
||||
|
||||
|
|
@ -96,6 +101,7 @@ builder.Services.AddControllers(options =>
|
|||
options.Filters.Add<UnitOfWorkFilter>();
|
||||
options.Filters.Add<LimitUserRequestAuthorization>();
|
||||
options.Filters.Add<TrialGlobalLimitActionFilter>();
|
||||
options.Filters.Add<RequestDuplicationFilter>();
|
||||
|
||||
})
|
||||
.AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理
|
||||
|
|
@ -111,8 +117,13 @@ builder.Services.AddAutoMapperSetup();
|
|||
builder.Services.AddEFSetup(_configuration, enviromentName);
|
||||
//Http 响应压缩
|
||||
builder.Services.AddResponseCompressionSetup();
|
||||
//Swagger Api 文档
|
||||
builder.Services.AddSwaggerSetup();
|
||||
|
||||
if (isOpenSwagger)
|
||||
{
|
||||
//Swagger Api 文档
|
||||
builder.Services.AddSwaggerSetup();
|
||||
}
|
||||
|
||||
//JWT Token 验证
|
||||
builder.Services.AddJWTAuthSetup(_configuration);
|
||||
|
||||
|
|
@ -126,7 +137,7 @@ builder.Services.AddFusionCache();
|
|||
builder.Services.AddhangfireSetup(_configuration);
|
||||
|
||||
//Serilog 日志可视化 LogDashboard日志
|
||||
builder.Services.AddLogDashboardSetup();
|
||||
//builder.Services.AddLogDashboardSetup();
|
||||
|
||||
//Dicom影像渲染图片 跨平台
|
||||
builder.Services.AddDicomSetup();
|
||||
|
|
@ -211,13 +222,18 @@ app.UseResponseCompression();
|
|||
app.UseStaticFiles();
|
||||
|
||||
//LogDashboard
|
||||
app.UseLogDashboard("/LogDashboard");
|
||||
//app.UseLogDashboard("/LogDashboard");
|
||||
|
||||
//hangfire
|
||||
app.UseHangfireConfig(env);
|
||||
|
||||
// Swagger
|
||||
SwaggerSetup.Configure(app, env);
|
||||
|
||||
if (isOpenSwagger)
|
||||
{
|
||||
SwaggerSetup.Configure(app, env);
|
||||
}
|
||||
|
||||
|
||||
//serilog 记录请求的用户信息
|
||||
app.UseSerilogConfig(env);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@
|
|||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Test_IRC"
|
||||
"ASPNETCORE_ENVIRONMENT": "Test_IRC",
|
||||
"ASPNETCORE_OpenSwagger": "true"
|
||||
},
|
||||
"applicationUrl": "http://localhost:6100"
|
||||
},
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 73 MiB |
|
|
@ -1,2 +1,3 @@
|
|||
{
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
using LogDashboard;
|
||||
using LogDashboard.Authorization;
|
||||
//using LogDashboard;
|
||||
//using LogDashboard.Authorization;
|
||||
|
||||
namespace IRaCIS.Core.API.Filter
|
||||
{
|
||||
//namespace IRaCIS.Core.API.Filter
|
||||
//{
|
||||
|
||||
public class LogDashBoardAuthFilter : ILogDashboardAuthorizationFilter
|
||||
{
|
||||
//在此可以利用 本系统的UerTypeEnum 判断
|
||||
public bool Authorization(LogDashboardContext context)
|
||||
{
|
||||
return context.HttpContext.User.Identity.IsAuthenticated;
|
||||
}
|
||||
}
|
||||
}
|
||||
// public class LogDashBoardAuthFilter : ILogDashboardAuthorizationFilter
|
||||
// {
|
||||
// //在此可以利用 本系统的UerTypeEnum 判断
|
||||
// public bool Authorization(LogDashboardContext context)
|
||||
// {
|
||||
// return context.HttpContext.User.Identity.IsAuthenticated;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
|
||||
using LogDashboard;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
//using LogDashboard;
|
||||
//using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public static class LogDashboardSetup
|
||||
{
|
||||
public static void AddLogDashboardSetup(this IServiceCollection services)
|
||||
{
|
||||
//IIS 配置虚拟路径部署,会出现IIS静态文件404
|
||||
services.AddLogDashboard(opt =>
|
||||
{
|
||||
//opt.PathMatch = "/api/LogDashboard";
|
||||
opt.PathMatch = "/LogDashboard";
|
||||
//namespace IRaCIS.Core.API
|
||||
//{
|
||||
// public static class LogDashboardSetup
|
||||
// {
|
||||
// public static void AddLogDashboardSetup(this IServiceCollection services)
|
||||
// {
|
||||
// //IIS 配置虚拟路径部署,会出现IIS静态文件404
|
||||
// services.AddLogDashboard(opt =>
|
||||
// {
|
||||
// //opt.PathMatch = "/api/LogDashboard";
|
||||
// opt.PathMatch = "/LogDashboard";
|
||||
|
||||
//opt.AddAuthorizationFilter(new LogDashboardBasicAuthFilter("admin", "zhizhun2018"));
|
||||
// //opt.AddAuthorizationFilter(new LogDashboardBasicAuthFilter("admin", "zhizhun2018"));
|
||||
|
||||
//opt.AddAuthorizationFilter(new LogDashBoardAuthFilter());
|
||||
// //opt.AddAuthorizationFilter(new LogDashBoardAuthFilter());
|
||||
|
||||
});
|
||||
// });
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,51 @@ namespace IRaCIS.Core.API
|
|||
}
|
||||
|
||||
|
||||
public class DateOnlyUniversalJsonConverter : JsonConverter
|
||||
{
|
||||
private readonly string _format;
|
||||
|
||||
public DateOnlyUniversalJsonConverter(string format = "yyyy-MM-dd")
|
||||
{
|
||||
_format = format;
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(DateOnly) || objectType == typeof(DateOnly?);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WriteValue(""); // null -> 空字符串
|
||||
return;
|
||||
}
|
||||
|
||||
var date = (DateOnly)value;
|
||||
if (date == default)
|
||||
{
|
||||
writer.WriteValue(""); // default(DateOnly) -> 空字符串
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValue(date.ToString(_format));
|
||||
}
|
||||
}
|
||||
|
||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var str = reader.TokenType == JsonToken.Null ? null : reader.Value?.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(str) || !DateOnly.TryParse(str, out var date))
|
||||
{
|
||||
return objectType == typeof(DateOnly?) ? null : default(DateOnly);
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
#region 废弃
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ namespace IRaCIS.Core.API
|
|||
//必须放在后面
|
||||
options.SerializerSettings.Converters.Add(services.BuildServiceProvider().GetService<JSONTimeZoneConverter>());
|
||||
|
||||
//options.SerializerSettings.Converters.Add(new DateOnlyUniversalJsonConverter("yyyy-MM-dd"));
|
||||
|
||||
|
||||
})
|
||||
.AddControllersAsServices()//动态webApi属性注入需要
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ namespace IRaCIS.Core.API
|
|||
var config = new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||
.MinimumLevel.Override("MassTransit", LogEventLevel.Warning)
|
||||
//https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Logging.md
|
||||
.MinimumLevel.Override("ZiggyCreatures.Caching.Fusion", LogEventLevel.Warning)
|
||||
|
||||
// Filter out ASP.NET Core infrastructre logs that are Information and below 日志太多了 一个请求 记录好几条
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning)
|
||||
|
|
@ -31,7 +35,7 @@ namespace IRaCIS.Core.API
|
|||
.Enrich.FromLogContext()
|
||||
.Filter.ByExcluding(logEvent => logEvent.Properties.ContainsKey("RequestPath") && logEvent.Properties["RequestPath"].ToString().Contains("/health"))
|
||||
.WriteTo.Console()
|
||||
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day);
|
||||
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day,retainedFileCountLimit:60);
|
||||
|
||||
#region 根据环境配置是否打开错误发送邮件通知
|
||||
|
||||
|
|
|
|||
|
|
@ -28,12 +28,13 @@ public static class ServiceCollectionSetup
|
|||
services.AddOptions().Configure<AliyunOSSOptions>(_configuration.GetSection("AliyunOSS"));
|
||||
services.AddOptions().Configure<ObjectStoreServiceOptions>(_configuration.GetSection("ObjectStoreService"));
|
||||
services.AddOptions().Configure<IRCEncreptOption>(_configuration.GetSection("EncrypteResponseConfig"));
|
||||
services.AddOptions().Configure<RequestDuplicationOptions>(_configuration.GetSection("RequestDuplicationOptions"));
|
||||
services.AddOptions().Configure<SystemPacsConfig>(_configuration.GetSection("SystemPacsConfig"));
|
||||
services.Configure<IRaCISBasicConfigOption>(_configuration.GetSection("IRaCISBasicConfig"));
|
||||
|
||||
services.Configure<ServiceVerifyConfigOption>(_configuration.GetSection("BasicSystemConfig"));
|
||||
|
||||
//转发头设置 获取真实IP
|
||||
//ת<EFBFBD><EFBFBD>ͷ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><>ȡ<EFBFBD><C8A1>ʵIP
|
||||
services.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
options.ForwardedHeaders =
|
||||
|
|
@ -51,7 +52,7 @@ public static class ServiceCollectionSetup
|
|||
|
||||
services.AddScoped<IObtainTaskAutoCancelJob, ObtainTaskAutoCancelJob>();
|
||||
|
||||
// 注册以Service 结尾的服务
|
||||
// ע<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Service <20><>β<EFBFBD>ķ<EFBFBD><C4B7><EFBFBD>
|
||||
services.Scan(scan => scan
|
||||
.FromAssemblies(typeof(BaseService).Assembly)
|
||||
.AddClasses(classes => classes.Where(t => t.Name.Contains("Service")))
|
||||
|
|
@ -70,23 +71,23 @@ public static class ServiceCollectionSetup
|
|||
#endregion
|
||||
|
||||
|
||||
// MediatR 进程内消息 事件解耦 从程序集中 注册命令和handler对应关系
|
||||
// MediatR <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ <20>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD> <20>ӳ<EFBFBD><D3B3><EFBFBD><EFBFBD><EFBFBD> ע<><D7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>handler<65><72>Ӧ<EFBFBD><D3A6>ϵ
|
||||
//builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<ConsistencyVerificationHandler>());
|
||||
|
||||
|
||||
|
||||
#region 历史废弃配置
|
||||
#region <EFBFBD><EFBFBD>ʷ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
//builder.Services.AddMemoryCache();
|
||||
////上传限制 配置
|
||||
////<EFBFBD>ϴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
//builder.Services.Configure<FormOptions>(options =>
|
||||
//{
|
||||
// options.MultipartBodyLengthLimit = int.MaxValue;
|
||||
// options.ValueCountLimit = int.MaxValue;
|
||||
// options.ValueLengthLimit = int.MaxValue;
|
||||
//});
|
||||
//IP 限流 可设置白名单 或者黑名单
|
||||
//IP <EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ð<EFBFBD><C3B0><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>ߺ<EFBFBD><DFBA><EFBFBD><EFBFBD><EFBFBD>
|
||||
//services.AddIpPolicyRateLimitSetup(_configuration);
|
||||
// 用户类型 策略授权
|
||||
// <EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȩ
|
||||
//services.AddAuthorizationPolicySetup(_configuration);
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -94,25 +95,25 @@ public static class ServiceCollectionSetup
|
|||
|
||||
}
|
||||
|
||||
#region Autofac 废弃
|
||||
#region Autofac <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
//public class AutofacModuleSetup : Autofac.Module
|
||||
//{
|
||||
// protected override void Load(ContainerBuilder containerBuilder)
|
||||
// {
|
||||
|
||||
// #region byzhouhang 20210917 此处注册泛型仓储 可以减少Domain层 和Infra.EFcore 两层 空的仓储接口定义和 仓储文件定义
|
||||
// #region byzhouhang 20210917 <EFBFBD>˴<EFBFBD>ע<EFBFBD>᷺<EFBFBD>Ͳִ<EFBFBD> <20><><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD>Domain<69><6E> <20><>Infra.EFcore <20><><EFBFBD><EFBFBD> <20>յIJִ<C4B2><D6B4>ӿڶ<D3BF><DAB6><EFBFBD><EFBFBD> <20>ִ<EFBFBD><D6B4>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD>
|
||||
|
||||
// containerBuilder.RegisterGeneric(typeof(Repository<>))
|
||||
// .As(typeof(IRepository<>)).InstancePerLifetimeScope();//注册泛型仓储
|
||||
// .As(typeof(IRepository<>)).InstancePerLifetimeScope();//ע<EFBFBD>᷺<EFBFBD>Ͳִ<EFBFBD>
|
||||
|
||||
// containerBuilder.RegisterType<Repository>().As<IRepository>().InstancePerLifetimeScope();
|
||||
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region 指定控制器也由autofac 来进行实例获取 https://www.cnblogs.com/xwhqwer/p/15320838.html
|
||||
// #region ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҳ<EFBFBD><EFBFBD>autofac <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><CAB5><EFBFBD><EFBFBD>ȡ https://www.cnblogs.com/xwhqwer/p/15320838.html
|
||||
|
||||
// //获取所有控制器类型并使用属性注入
|
||||
// //<EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD>п<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ͳ<EFBFBD>ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><EFBFBD>
|
||||
// containerBuilder.RegisterAssemblyTypes(typeof(BaseService).Assembly)
|
||||
// .Where(type => typeof(IDynamicWebApi).IsAssignableFrom(type))
|
||||
// .PropertiesAutowired();
|
||||
|
|
@ -128,7 +129,7 @@ public static class ServiceCollectionSetup
|
|||
// //containerBuilder.RegisterType<UserInfo>().As<IUserInfo>().InstancePerLifetimeScope();
|
||||
|
||||
|
||||
// //注册hangfire任务 依赖注入
|
||||
// //ע<EFBFBD><EFBFBD>hangfire<EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>ע<EFBFBD><D7A2>
|
||||
// containerBuilder.RegisterType<ObtainTaskAutoCancelJob>().As<IObtainTaskAutoCancelJob>().InstancePerDependency();
|
||||
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -16,27 +16,30 @@ namespace IRaCIS.Core.API;
|
|||
|
||||
public enum SwaggerVersion
|
||||
{
|
||||
[Description("医生模块")]
|
||||
[Description("文件记录(FileRecord)")]
|
||||
FileRecord = -1,
|
||||
|
||||
[Description("医生模块(Reviewer)")]
|
||||
Reviewer = 1,
|
||||
[Description("项目模块")]
|
||||
[Description("项目模块(Trial)")]
|
||||
Trial = 2,
|
||||
[Description("入组模块")]
|
||||
[Description("入组模块(Enroll)")]
|
||||
Enroll = 3,
|
||||
[Description("工作量模块")]
|
||||
[Description("工作量模块(Workload)")]
|
||||
Workload = 4,
|
||||
[Description("通用信息获取")]
|
||||
[Description("通用信息获取(Common)")]
|
||||
Common = 5,
|
||||
[Description("机构信息模块")]
|
||||
[Description("机构信息模块(Institution)")]
|
||||
Institution = 6,
|
||||
[Description("统计模块")]
|
||||
[Description("统计模块(DashboardStatistics)")]
|
||||
DashboardStatistics = 7,
|
||||
[Description("财务模块")]
|
||||
[Description("财务模块(Financial)")]
|
||||
Financial = 8,
|
||||
[Description("管理模块")]
|
||||
[Description("管理模块(Management)")]
|
||||
Management =9,
|
||||
[Description("影像模块")]
|
||||
[Description("影像模块(Image)")]
|
||||
Image =10,
|
||||
[Description("读片模块")]
|
||||
[Description("读片模块(Reading)")]
|
||||
Reading =11
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ namespace IRaCIS.Core.API
|
|||
hangFireConfig.UseSqlServerStorage(hangFireConnStr, new SqlServerStorageOptions()
|
||||
{
|
||||
SchemaName = "dbo",
|
||||
}).UseRecommendedSerializerSettings().UseSimpleAssemblyNameTypeSerializer();
|
||||
}).UseRecommendedSerializerSettings();
|
||||
// 移除 UseSimpleAssemblyNameTypeSerializer() 以避免类型加载问题
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"RemoteNew": "Server=47.117.164.182,1434;Database=Event_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
|
||||
"Hangfire": "Server=47.117.164.182,1434;Database=Event_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
|
||||
"RemoteNew": "Server=101.132.253.119,1435;Database=irc_Prpd_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
|
||||
"Hangfire": "Server=101.132.253.119,1435;Database=irc_Hangfire_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
|
||||
},
|
||||
"ObjectStoreService": {
|
||||
"ObjectStoreUse": "AliyunOSS",
|
||||
|
|
@ -16,11 +16,11 @@
|
|||
"RegionId": "cn-shanghai",
|
||||
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
|
||||
"EndPoint": "https://oss-cn-shanghai.aliyuncs.com",
|
||||
"AccessKeyId": "LTAI5tNRTsqL6aWmHkDmTwoH",
|
||||
"AccessKeySecret": "7mtGz3qrYWI6JMMBZiLeC119VWicZH",
|
||||
"RoleArn": "acs:ram::1899121822495495:role/irc-oss-access",
|
||||
"BucketName": "zy-irc-store",
|
||||
"ViewEndpoint": "https://zy-irc-cache.oss-cn-shanghai.aliyuncs.com",
|
||||
"AccessKeyId": "LTAI5tFUCCmz5TwghZHsj45Y",
|
||||
"AccessKeySecret": "8evrBy1fVfzJG25i67Jm0xqn9Xcw2T",
|
||||
"RoleArn": "acs:ram::1078130221702011:role/uat-oss-access",
|
||||
"BucketName": "tl-med-irc-event-store",
|
||||
"ViewEndpoint": "https://tl-med-irc-event-store.oss-cn-shanghai.aliyuncs.com",
|
||||
"Region": "oss-cn-shanghai",
|
||||
"DurationSeconds": 7200
|
||||
},
|
||||
|
|
@ -35,7 +35,8 @@
|
|||
},
|
||||
|
||||
"BasicSystemConfig": {
|
||||
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
"OpenUserComplexPassword": true,
|
||||
|
||||
"OpenSignDocumentBeforeWork": true,
|
||||
|
|
@ -52,11 +53,18 @@
|
|||
|
||||
"IsNeedChangePassWord": true,
|
||||
|
||||
"ChangePassWordDays": 90
|
||||
"ChangePassWordDays": 90,
|
||||
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 2,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyDays": 1
|
||||
},
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 465,
|
||||
"Host": "smtp.qiye.aliyun.com",
|
||||
"Imap": "imap.qiye.aliyun.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "uat@extimaging.com",
|
||||
"FromName": "UAT_IRC",
|
||||
"AuthorizationCode": "SHzyyl2021",
|
||||
|
|
@ -66,7 +74,13 @@
|
|||
"CompanyShortName": "Extensive Imaging",
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
"IsEnv_US": false
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@
|
|||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
//"RemoteNew": "Server=101.132.193.237,1434;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
|
||||
//"Hangfire": "Server=101.132.193.237,1434;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
|
||||
"RemoteNew": "Server=prod_mssql_standard,1433;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
|
||||
"Hangfire": "Server=prod_mssql_standard,1433;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
|
||||
"RemoteNew": "Server=101.132.193.237,1434;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
|
||||
"Hangfire": "Server=101.132.193.237,1434;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
|
||||
//"RemoteNew": "Server=prod_mssql_standard,1433;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
|
||||
//"Hangfire": "Server=prod_mssql_standard,1433;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
|
||||
},
|
||||
"ObjectStoreService": {
|
||||
"ObjectStoreUse": "AliyunOSS",
|
||||
|
|
@ -36,14 +36,15 @@
|
|||
}
|
||||
},
|
||||
"BasicSystemConfig": {
|
||||
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
"OpenUserComplexPassword": true,
|
||||
"OpenSignDocumentBeforeWork": true,
|
||||
|
||||
"OpenLoginLimit": true,
|
||||
"LoginMaxFailCount": 5,
|
||||
"LoginFailLockMinutes": 30,
|
||||
"AutoLoginOutMinutes": 360,
|
||||
"AutoLoginOutMinutes": 120,
|
||||
|
||||
"OpenLoginMFA": false,
|
||||
|
||||
|
|
@ -51,29 +52,43 @@
|
|||
"ReadingRestTimeMin": 10,
|
||||
|
||||
"IsNeedChangePassWord": true,
|
||||
"ChangePassWordDays": 90
|
||||
"ChangePassWordDays": 90,
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 2,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyDays": 1
|
||||
},
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 465,
|
||||
"Host": "smtp.qiye.aliyun.com",
|
||||
"Imap": "imap.qiye.aliyun.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "irc@extimaging.com",
|
||||
"FromName": "irc",
|
||||
"FromName": "IRC Imaging System",
|
||||
"AuthorizationCode": "ExtImg@2022",
|
||||
"SiteUrl": "http://irc.extimaging.com/login",
|
||||
"OrganizationName": "Extlmaging",
|
||||
"OrganizationNameCN": "Extlmaging",
|
||||
"SystemShortName": "IRC",
|
||||
"OrganizationName": "ExtImaging",
|
||||
"OrganizationNameCN": "ExtImaging",
|
||||
"CompanyName": "Extensive Imaging",
|
||||
"CompanyNameCN": "上海展影医疗科技有限公司",
|
||||
"CompanyShortName": "Extensive Imaging",
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
"IsEnv_US": false,
|
||||
"IsOpenErrorNoticeEmail": false,
|
||||
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
"SystemPacsConfig": {
|
||||
"Port": "11113",
|
||||
"IP": "101.132.193.237"
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,99 +1,183 @@
|
|||
{
|
||||
// 日志信息
|
||||
"Logging": {
|
||||
// 日志等级
|
||||
"LogLevel": {
|
||||
// 默认日志等级
|
||||
"Default": "Information",
|
||||
// 调试日志等级
|
||||
"Microsoft": "Warning",
|
||||
// ASP.NET Core 日志等级
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
// 数据库链接字符串
|
||||
"ConnectionStrings": {
|
||||
// 本地数据库链接字符串
|
||||
"RemoteNew": "Server=106.14.89.110,1435;Database=Test_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
|
||||
// Hangfire 定时任务数据库链接字符串
|
||||
"Hangfire": "Server=106.14.89.110,1435;Database=Test_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
|
||||
},
|
||||
|
||||
// 对象存储服务配置
|
||||
"ObjectStoreService": {
|
||||
|
||||
// 使用的对象存储服务类型
|
||||
"ObjectStoreUse": "AliyunOSS",
|
||||
|
||||
// 阿里云对象存储服务的配置
|
||||
"AliyunOSS": {
|
||||
// 阿里云 OSS 的 Region ID
|
||||
"RegionId": "cn-shanghai",
|
||||
// 阿里云 OSS 的内部访问端点
|
||||
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
|
||||
// 阿里云 OSS 的外部访问端点
|
||||
"EndPoint": "https://oss-cn-shanghai.aliyuncs.com",
|
||||
// 阿里云 OSS 的访问密钥 ID
|
||||
"AccessKeyId": "LTAI5tRRZehUp2V9pyTPtAJm",
|
||||
// 阿里云 OSS 的访问密钥 Secret
|
||||
"AccessKeySecret": "FLizxkHsMm4CGYHtkV8E3PNJJZU7oV",
|
||||
// 阿里云 OSS 的角色 ARN
|
||||
"RoleArn": "acs:ram::1899121822495495:role/dev-oss-access",
|
||||
// 阿里云 OSS 的Bucket名称
|
||||
"BucketName": "zy-irc-test-store",
|
||||
"ViewEndpoint": "https://zy-irc-test-store.oss-cn-shanghai.aliyuncs.com",
|
||||
// 阿里云 OSS 的访问端点
|
||||
"ViewEndpoint": "https://zy-irc-test-dev-cache.oss-cn-shanghai.aliyuncs.com",
|
||||
// 阿里云 OSS 的预览端点
|
||||
"Region": "oss-cn-shanghai",
|
||||
"DurationSeconds": 7200
|
||||
// 阿里云 OSS 的临时访问凭证有效时间(秒)
|
||||
"DurationSeconds": 7200,
|
||||
// 阿里云 OSS 的预览端点
|
||||
"PreviewEndpoint": "https://test-oss.test.extimaging.com"
|
||||
},
|
||||
// MinIO 对象存储服务的配置
|
||||
"MinIO": {
|
||||
// MinIO 的访问端点
|
||||
"EndPoint": "hir-oss.test.extimaging.com",
|
||||
// MinIO 的端口
|
||||
"Port": "443",
|
||||
// 是否使用 SSL
|
||||
"UseSSL": true,
|
||||
// MinIO 的角色 ARN
|
||||
"AccessKey": "fbStsVYCIPKHQneeqMwD",
|
||||
// MinIO 的访问密钥
|
||||
"SecretKey": "TzgvyA3zGXMUnpilJNUlyMYHfosl1hBMl6lxPmjy",
|
||||
// MinIO 的BucketName
|
||||
"BucketName": "irc-test",
|
||||
// MinIO 的访问端点
|
||||
"ViewEndpoint": "https://hir-oss.test.extimaging.com/irc-test"
|
||||
},
|
||||
// AWS S3 对象存储服务的配置
|
||||
"AWS": {
|
||||
// AWS S3 的Region
|
||||
"Region": "us-east-1",
|
||||
// AWS S3 的内部访问端点
|
||||
"EndPoint": "s3.us-east-1.amazonaws.com",
|
||||
// 是否使用 SSL
|
||||
"UseSSL": true,
|
||||
// AWS S3 的角色 ARN
|
||||
"RoleArn": "arn:aws:iam::471112624751:role/uat_s3_access",
|
||||
// AWS S3 的访问密钥 ID
|
||||
"AccessKeyId": "AKIAW3MEAFJX7IPXISP4",
|
||||
// AWS S3 的访问密钥 Secret
|
||||
"SecretAccessKey": "Pgrg3le5jPxZQ7MR1yYNS30J0XRyJeKVyIIjElXc",
|
||||
// AWS S3 的Bucket名称
|
||||
"BucketName": "ei-med-s3-lili-uat-store",
|
||||
// AWS S3 的访问端点
|
||||
"ViewEndpoint": "https://ei-med-s3-lili-uat-store.s3.amazonaws.com",
|
||||
// AWS S3 的持续数秒
|
||||
"DurationSeconds": 7200
|
||||
}
|
||||
},
|
||||
|
||||
// 系统配置
|
||||
"BasicSystemConfig": {
|
||||
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
// 打开用户复杂密码
|
||||
"OpenUserComplexPassword": false,
|
||||
// 是否在开始工作前强制签署电子知情同意书
|
||||
"OpenSignDocumentBeforeWork": false,
|
||||
|
||||
// 是否启用登录失败次数限制(防暴力破解)
|
||||
"OpenLoginLimit": false,
|
||||
// 连续登录失败多少次后触发锁定
|
||||
"LoginMaxFailCount": 5,
|
||||
// 触发锁定后账号锁定时长(分钟)
|
||||
"LoginFailLockMinutes": 1,
|
||||
// 无操作自动登出的超时时间(分钟)
|
||||
"AutoLoginOutMinutes": 10,
|
||||
|
||||
// 是否启用多因子登录认证(MFA)
|
||||
"OpenLoginMFA": false,
|
||||
|
||||
// 连续阅片的最长工作时间(分钟),超时后强制休息
|
||||
"ContinuousReadingTimeMin": 120,
|
||||
// 强制休息时长(分钟)
|
||||
"ReadingRestTimeMin": 10,
|
||||
|
||||
// 是否强制用户定期修改密码
|
||||
"IsNeedChangePassWord": true,
|
||||
// 密码有效期(天),到期后必须修改
|
||||
"ChangePassWordDays": 90,
|
||||
|
||||
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 2,
|
||||
// 是否打开项目关联删除
|
||||
"OpenTrialRelationDelete": true,
|
||||
|
||||
"ThirdPdfUrl": "http://106.14.89.110:30088/api/v1/convert/file/pdf"
|
||||
// 转换PDF服务配置
|
||||
"ThirdPdfUrl": "http://106.14.89.110:30088/api/v1/convert/file/pdf",
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
},
|
||||
|
||||
// 邮件服务配置(用于系统通知、找回密码、错误报警等)
|
||||
"SystemEmailSendConfig": {
|
||||
// SMTP端口
|
||||
"Port": 465,
|
||||
// 企业邮箱SMTP服务器地址
|
||||
"Host": "smtp.qiye.aliyun.com",
|
||||
|
||||
"Imap": "imap.qiye.aliyun.com",
|
||||
|
||||
"ImapPort": 993,
|
||||
// 发件人邮箱地址
|
||||
"FromEmail": "test@extimaging.com",
|
||||
"FromName": "Test_IRC",
|
||||
// 发件人显示名称
|
||||
"FromName": "Test IRC Imaging System",
|
||||
// SMTP授权码
|
||||
"AuthorizationCode": "SHzyyl2021",
|
||||
// 系统对外访问地址
|
||||
"SiteUrl": "http://irc.test.extimaging.com/login",
|
||||
|
||||
"OrganizationName": "Extlmaging",
|
||||
"OrganizationNameCN": "Extlmaging",
|
||||
// 系统简称
|
||||
"SystemShortName": "IRC",
|
||||
// 组织英文名称
|
||||
"OrganizationName": "ExtImaging",
|
||||
// 组织中文名称
|
||||
"OrganizationNameCN": "ExtImaging",
|
||||
// 公司英文全称
|
||||
"CompanyName": "Extensive Imaging",
|
||||
// 公司中文全称
|
||||
"CompanyNameCN": "上海展影医疗科技有限公司",
|
||||
// 公司英文简称
|
||||
"CompanyShortName": "Extensive Imaging",
|
||||
// 公司中文简称
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
// 是否为国际版环境
|
||||
"IsEnv_US": false,
|
||||
// 是否开启系统异常邮件报警
|
||||
"IsOpenErrorNoticeEmail": false,
|
||||
"ErrorNoticeEmailList": ["872297557@qq.com"]
|
||||
// 邮箱格式校验正则表达式
|
||||
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
||||
// 接收系统异常报警的邮箱列表
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
|
||||
// PACS 连接配置
|
||||
"SystemPacsConfig": {
|
||||
// PACS服务器端口
|
||||
"Port": "11113",
|
||||
// PACS服务器IP地址
|
||||
"IP": "106.14.89.110"
|
||||
},
|
||||
// 重复请求配置
|
||||
"RequestDuplicationOptions": {
|
||||
// 是否启用重复请求检测
|
||||
"IsEnabled": true,
|
||||
// 重复请求时间窗口(毫秒)
|
||||
"DuplicationWindowMs": 200,
|
||||
// 缓存请求时间(秒)
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@
|
|||
},
|
||||
|
||||
"BasicSystemConfig": {
|
||||
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
"OpenUserComplexPassword": false,
|
||||
|
||||
"OpenSignDocumentBeforeWork": false,
|
||||
|
|
@ -75,13 +76,15 @@
|
|||
"SystemEmailSendConfig": {
|
||||
"Port": 465,
|
||||
"Host": "smtp.qiye.aliyun.com",
|
||||
"Imap": "imap.qiye.aliyun.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "test@extimaging.com",
|
||||
"FromName": "Test_IRC",
|
||||
"AuthorizationCode": "SHzyyl2021",
|
||||
"SiteUrl": "http://irc.test.extimaging.com/login",
|
||||
|
||||
"OrganizationName": "Extlmaging",
|
||||
"OrganizationNameCN": "Extlmaging",
|
||||
"OrganizationName": "ExtImaging",
|
||||
"OrganizationNameCN": "ExtImaging",
|
||||
"CompanyName": "Extensive Imaging",
|
||||
"CompanyNameCN": "上海展影医疗科技有限公司",
|
||||
"CompanyShortName": "Extensive Imaging",
|
||||
|
|
@ -91,5 +94,12 @@
|
|||
"SystemPacsConfig": {
|
||||
"Port": "11113",
|
||||
"IP": "106.14.89.110"
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@
|
|||
}
|
||||
},
|
||||
"BasicSystemConfig": {
|
||||
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
"OpenUserComplexPassword": true,
|
||||
"OpenSignDocumentBeforeWork": true,
|
||||
|
||||
|
|
@ -54,19 +55,25 @@
|
|||
|
||||
"IsNeedChangePassWord": true,
|
||||
"ChangePassWordDays": 90,
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 1,
|
||||
|
||||
|
||||
"OpenTrialRelationDelete": false
|
||||
"OpenTrialRelationDelete": false,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyDays": 1
|
||||
|
||||
},
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 587,
|
||||
"Host": "smtp-mail.outlook.com",
|
||||
"Imap": "imap-mail.outlook.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "donotreply@elevateimaging.ai",
|
||||
"FromName": "LiLi",
|
||||
"FromName": "LiLi System",
|
||||
"AuthorizationCode": "Q#669869497420ul",
|
||||
|
||||
"SystemShortName": "LiLi",
|
||||
"OrganizationName": "Elevate Imaging",
|
||||
"OrganizationNameCN": "Elevate Imaging",
|
||||
"CompanyName": "Elevate Imaging Inc.",
|
||||
|
|
@ -76,12 +83,20 @@
|
|||
"SiteUrl": "https://lili.elevateimaging.ai/login",
|
||||
"IsEnv_US": true,
|
||||
"IsOpenErrorNoticeEmail": false,
|
||||
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
|
||||
"SystemPacsConfig": {
|
||||
"Port": "104",
|
||||
"IP": "44.210.231.169"
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@
|
|||
}
|
||||
},
|
||||
"BasicSystemConfig": {
|
||||
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
"OpenUserComplexPassword": true,
|
||||
|
||||
"OpenSignDocumentBeforeWork": true,
|
||||
|
|
@ -64,17 +65,23 @@
|
|||
"IsNeedChangePassWord": true,
|
||||
|
||||
"ChangePassWordDays": 90,
|
||||
|
||||
"OpenLoginMFA": true
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 1,
|
||||
"OpenLoginMFA": true,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyDays": 1
|
||||
},
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 587,
|
||||
"Host": "smtp-mail.outlook.com",
|
||||
"Imap": "imap-mail.outlook.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "donotreply@elevateimaging.ai",
|
||||
"FromName": "LiLi",
|
||||
"FromName": "LiLi System",
|
||||
"AuthorizationCode": "Q#669869497420ul",
|
||||
|
||||
"SystemShortName": "LiLi",
|
||||
"OrganizationName": "Elevate Imaging",
|
||||
"OrganizationNameCN": "Elevate Imaging",
|
||||
"CompanyName": "Elevate Imaging Inc.",
|
||||
|
|
@ -90,6 +97,13 @@
|
|||
"SystemPacsConfig": {
|
||||
"Port": "104",
|
||||
"IP": "3.226.182.187"
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@
|
|||
}
|
||||
},
|
||||
"BasicSystemConfig": {
|
||||
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
"OpenUserComplexPassword": true,
|
||||
"OpenSignDocumentBeforeWork": true,
|
||||
|
||||
|
|
@ -63,16 +64,23 @@
|
|||
"ReadingRestTimeMin": 10,
|
||||
|
||||
"IsNeedChangePassWord": true,
|
||||
"ChangePassWordDays": 90
|
||||
"ChangePassWordDays": 90,
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 1,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyDays": 1
|
||||
},
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 587,
|
||||
"Host": "smtp-mail.outlook.com",
|
||||
"Imap": "imap-mail.outlook.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "donotreply@elevateimaging.ai",
|
||||
"FromName": "LiLi",
|
||||
"FromName": "LiLi System",
|
||||
"AuthorizationCode": "Q#669869497420ul",
|
||||
|
||||
"SystemShortName": "LiLi",
|
||||
"OrganizationName": "Elevate Imaging",
|
||||
"OrganizationNameCN": "Elevate Imaging",
|
||||
"CompanyName": "Elevate Imaging Inc.",
|
||||
|
|
@ -82,12 +90,19 @@
|
|||
"SiteUrl": "https://lili.uat.elevateimaging.ai/login",
|
||||
"IsEnv_US": true,
|
||||
"IsOpenErrorNoticeEmail": false,
|
||||
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
|
||||
"SystemPacsConfig": {
|
||||
"Port": "104",
|
||||
"IP": "3.226.182.187"
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@
|
|||
},
|
||||
|
||||
"BasicSystemConfig": {
|
||||
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
"OpenUserComplexPassword": true,
|
||||
"OpenSignDocumentBeforeWork": true,
|
||||
|
||||
|
|
@ -62,7 +63,7 @@
|
|||
"LoginMaxFailCount": 5,
|
||||
|
||||
"LoginFailLockMinutes": 30,
|
||||
"AutoLoginOutMinutes": 60,
|
||||
"AutoLoginOutMinutes": 120,
|
||||
|
||||
"OpenLoginMFA": false,
|
||||
|
||||
|
|
@ -70,30 +71,46 @@
|
|||
"ReadingRestTimeMin": 10,
|
||||
|
||||
"IsNeedChangePassWord": true,
|
||||
"ChangePassWordDays": 90
|
||||
"ChangePassWordDays": 90,
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 2,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyDays": 1
|
||||
|
||||
},
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 465,
|
||||
"Host": "smtp.qiye.aliyun.com",
|
||||
"Imap": "imap.qiye.aliyun.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "uat@extimaging.com",
|
||||
"FromName": "UAT_IRC",
|
||||
"FromName": "Uat IRC Imaging System",
|
||||
"AuthorizationCode": "SHzyyl2021",
|
||||
"SiteUrl": "http://irc.uat.extimaging.com/login",
|
||||
"OrganizationName": "Extlmaging",
|
||||
"OrganizationNameCN": "Extlmaging",
|
||||
|
||||
"SystemShortName": "IRC",
|
||||
"OrganizationName": "ExtImaging",
|
||||
"OrganizationNameCN": "ExtImaging",
|
||||
"CompanyName": "Extensive Imaging",
|
||||
"CompanyNameCN": "上海展影医疗科技有限公司",
|
||||
"CompanyShortName": "Extensive Imaging",
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
"IsEnv_US": false,
|
||||
"IsOpenErrorNoticeEmail": false,
|
||||
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
|
||||
"SystemPacsConfig": {
|
||||
"Port": "11113",
|
||||
"IP": "101.132.253.119"
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"SecurityKey": "ShangHaiZhanYing_SecurityKey_SHzyyl@2021",
|
||||
"Issuer": "Extimaging",
|
||||
"Audience": "EICS",
|
||||
"TokenExpireDays": "7"
|
||||
"TokenExpireMinute": "10080"//7天
|
||||
},
|
||||
"IpRateLimiting": {
|
||||
"EnableEndpointRateLimiting": true,
|
||||
|
|
@ -74,5 +74,12 @@
|
|||
"redirect_uri": "https://oauthlogin.net/oauth/githubcallback",
|
||||
"scope": "repo"
|
||||
}
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ namespace IRaCIS.Core.Application.Auth
|
|||
/// <summary>
|
||||
/// 过期时间
|
||||
/// </summary>
|
||||
public int TokenExpireDays { get; set; }
|
||||
public int TokenExpireMinute { get; set; }
|
||||
|
||||
//public Dictionary<string, object> Claims { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ namespace IRaCIS.Core.Application.Auth
|
|||
public interface ITokenService
|
||||
{
|
||||
string GetToken(UserTokenInfo user);
|
||||
|
||||
bool IsTokenExpired(string token);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -47,13 +49,27 @@ namespace IRaCIS.Core.Application.Auth
|
|||
signingCredentials: _jwtSetting.Credentials,
|
||||
claims: claims,
|
||||
notBefore: DateTime.Now,
|
||||
expires: DateTime.Now.AddDays(_jwtSetting.TokenExpireDays)
|
||||
expires: DateTime.Now.AddMinutes(_jwtSetting.TokenExpireMinute)
|
||||
);
|
||||
|
||||
string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
|
||||
return jwtToken;
|
||||
|
||||
}
|
||||
|
||||
public bool IsTokenExpired(string token)
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
try
|
||||
{
|
||||
var jwtToken = handler.ReadJwtToken(token);
|
||||
return jwtToken.ValidTo < DateTime.UtcNow;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true; // 无效 Token 也视为已过期
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.BusinessFilter.LegacyController
|
||||
{
|
||||
using Database.Api;
|
||||
using DocumentFormat.OpenXml.InkML;
|
||||
using IRaCIS.Core.Application.Service.Common;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Components.Endpoints;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Minio.Helper;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Database.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求拦截 请求前后的操作
|
||||
/// </summary>
|
||||
/// <param name="logger">logger</param>
|
||||
/// <param name="accessor">loggerHelper</param>
|
||||
public class RequestDuplicationFilter(ILogger<RequestDuplicationFilter> logger, IHttpContextAccessor accessor, IUserInfo _userInfo, IStringLocalizer _localizer, IOptionsMonitor<RequestDuplicationOptions> RequestDuplicationOptionsMonitor) : ExceptionFilterAttribute, IAsyncActionFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 传入的参数
|
||||
/// </summary>
|
||||
public string Intoparam { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 这个是正常记录(请求刚进入的时候)
|
||||
/// </summary>
|
||||
/// <param name="context">context</param>
|
||||
/// <param name="next">next</param>
|
||||
/// <returns>返回的对象</returns>
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
if (RequestDuplicationOptionsMonitor.CurrentValue.IsEnabled)
|
||||
{
|
||||
Dictionary<string, object?> dic = new Dictionary<string, object?>();
|
||||
var desc = context.ActionDescriptor as ControllerActionDescriptor;
|
||||
foreach (var p in desc.Parameters)
|
||||
{
|
||||
// 关键判断:绑定源是否是 Services
|
||||
if (p.BindingInfo?.BindingSource == BindingSource.Services) continue;
|
||||
// 普通参数,取值
|
||||
if (context.ActionArguments.TryGetValue(p.Name, out var value) && value != null)
|
||||
{
|
||||
dic.Add(p.Name, value);
|
||||
}
|
||||
}
|
||||
|
||||
this.Intoparam = JsonConvert.SerializeObject(dic);
|
||||
try
|
||||
{
|
||||
this.RequestDuplication();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var resultContext = await next();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private void RequestDuplication()
|
||||
{
|
||||
var requestPath = accessor?.HttpContext?.Request?.Path.ToString() ?? string.Empty;
|
||||
|
||||
// 验证请求频繁情况
|
||||
if (
|
||||
!requestPath
|
||||
.Split("/", StringSplitOptions.RemoveEmptyEntries)
|
||||
.Any(segment => segment.StartsWith("get", StringComparison.OrdinalIgnoreCase)) &&
|
||||
_userInfo.UserRoleId != default(Guid)&&
|
||||
RequestDuplicationOptionsMonitor.CurrentValue.IsEnabled &&
|
||||
!RequestDuplicationOptionsMonitor.CurrentValue.ExcludePaths.Contains(requestPath))
|
||||
{
|
||||
RequestInfo requestInfo = new RequestInfo
|
||||
{
|
||||
UserRoleId = _userInfo.UserRoleId,
|
||||
RequestPath = requestPath,
|
||||
ParameterHash = GenerateParameterHash(this.Intoparam),
|
||||
RequestTime = DateTime.Now
|
||||
};
|
||||
|
||||
IRCSystemInfo.RequestRecordList= IRCSystemInfo.RequestRecordList.Where(x => x.RequestTime >= DateTime.Now.AddSeconds(-RequestDuplicationOptionsMonitor.CurrentValue.CacheTimeSeconds)).ToList();
|
||||
|
||||
|
||||
var requestsTimes = IRCSystemInfo.RequestRecordList.Any(x=>
|
||||
x.RequestTime>= requestInfo.RequestTime.AddMilliseconds(-RequestDuplicationOptionsMonitor.CurrentValue.DuplicationWindowMs)&&
|
||||
x.RequestKey== requestInfo.RequestKey);
|
||||
if (requestsTimes)
|
||||
{
|
||||
throw new BusinessValidationFailedException(_localizer["RequestDuplicationFilter_RequestDuplication"], ApiResponseCodeEnum.BusinessValidationFailed);
|
||||
}
|
||||
IRCSystemInfo.RequestRecordList.Add(requestInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public string GenerateParameterHash(string parameters)
|
||||
{
|
||||
if (string.IsNullOrEmpty(parameters))
|
||||
return string.Empty;
|
||||
|
||||
using var sha256 = SHA256.Create();
|
||||
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(parameters));
|
||||
return Convert.ToBase64String(hashBytes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IRaCIS.Core.Application.Service.BusinessFilter;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ namespace IRaCIS.Core.Domain.Share;
|
|||
[Description("多环境 配置环境实体")]
|
||||
public class ServiceVerifyConfigOption
|
||||
{
|
||||
public bool QCRiskControl { get; set; }
|
||||
public bool OpenUserComplexPassword { get; set; }
|
||||
|
||||
public bool OpenSignDocumentBeforeWork { get; set; }
|
||||
|
|
@ -34,8 +35,12 @@ public class ServiceVerifyConfigOption
|
|||
[Description("修改密码的天数")]
|
||||
public int ChangePassWordDays { get; set; }
|
||||
|
||||
public SysTemplateType TemplateType { get; set; }
|
||||
|
||||
public string ThirdPdfUrl { get; set; }
|
||||
|
||||
public int UserMFAVerifyMinutes { get; set; } = 1440;
|
||||
|
||||
}
|
||||
|
||||
public class SystemEmailSendConfig
|
||||
|
|
@ -43,6 +48,10 @@ public class SystemEmailSendConfig
|
|||
public int Port { get; set; }
|
||||
|
||||
public string Host { get; set; } = string.Empty;
|
||||
|
||||
public string Imap { get; set; } = string.Empty;
|
||||
|
||||
public int ImapPort { get; set; }
|
||||
public string FromEmail { get; set; } = string.Empty;
|
||||
|
||||
public string FromName { get; set; } = string.Empty;
|
||||
|
|
@ -51,6 +60,8 @@ public class SystemEmailSendConfig
|
|||
|
||||
public string SiteUrl { get; set; } = string.Empty;
|
||||
|
||||
public string SystemShortName { get; set; } = string.Empty;
|
||||
|
||||
public string OrganizationName { get; set; } = string.Empty;
|
||||
public string OrganizationNameCN { get; set; } = string.Empty;
|
||||
|
||||
|
|
@ -66,11 +77,14 @@ public class SystemEmailSendConfig
|
|||
|
||||
public bool IsOpenErrorNoticeEmail { get; set; }
|
||||
|
||||
public List<string> ErrorNoticeEmailList { get; set; } =new List<string>();
|
||||
public string EmailRegexStr { get; set; }
|
||||
|
||||
public List<string> ErrorNoticeEmailList { get; set; } = new List<string>();
|
||||
}
|
||||
|
||||
public class SystemEmailSendConfigView
|
||||
{
|
||||
public string SystemShortName { get; set; } = string.Empty;
|
||||
public string CompanyName { get; set; } = string.Empty;
|
||||
|
||||
public string CompanyNameCN { get; set; } = string.Empty;
|
||||
|
|
@ -78,6 +92,8 @@ public class SystemEmailSendConfigView
|
|||
public string CompanyShortName { get; set; } = string.Empty;
|
||||
|
||||
public string CompanyShortNameCN { get; set; } = string.Empty;
|
||||
|
||||
public string EmailRegexStr { get; set; }
|
||||
}
|
||||
|
||||
public class SystemPacsConfig
|
||||
|
|
@ -96,6 +112,54 @@ public class IRCEncreptOption
|
|||
public List<string> ApiPathList { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求缓存配置
|
||||
/// </summary>
|
||||
public class RequestDuplicationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 缓存时间(秒),默认5秒
|
||||
/// </summary>
|
||||
public int CacheTimeSeconds { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// 重复请求检测时间窗口(毫秒),默认500毫秒
|
||||
/// </summary>
|
||||
public int DuplicationWindowMs { get; set; } = 500;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用防重复请求
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 需要排除的路径(不进行重复检测)
|
||||
/// </summary>
|
||||
public List<string> ExcludePaths { get; set; } = new List<string>();
|
||||
}
|
||||
|
||||
public static class IRCSystemInfo
|
||||
{
|
||||
public static List<RequestInfo> RequestRecordList { get; set; } = new List<RequestInfo>();
|
||||
}
|
||||
|
||||
public class RequestInfo
|
||||
{
|
||||
public Guid UserRoleId { get; set; }
|
||||
|
||||
public string RequestPath { get; set; } = string.Empty;
|
||||
|
||||
public string ParameterHash { get; set; } = string.Empty;
|
||||
|
||||
public DateTime RequestTime { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 请求的唯一标识
|
||||
/// </summary>
|
||||
public string RequestKey => $"{UserRoleId}_{RequestPath}_{ParameterHash}";
|
||||
}
|
||||
|
||||
public class IRaCISBasicConfigOption
|
||||
{
|
||||
public string DoctorCodePrefix { get; set; }
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ global using AutoMapper;
|
|||
global using IRaCIS.Core.Domain.Share;
|
||||
global using IRaCIS.Core.Application.BusinessFilter;
|
||||
global using IdentityUser = IRaCIS.Core.Domain.Models.IdentityUser;
|
||||
global using Serilog;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ public static class CacheKeys
|
|||
/// <returns></returns>
|
||||
public static string StartRestTime(Guid userId) => $"{userId}StartRestTime";
|
||||
|
||||
//每个用户 每个浏览器独立时间
|
||||
public static string UserMFAVerifyPass(Guid userId,string browserFingerprint) => $"UserMFAVerifyPass:{userId}:{browserFingerprint}";
|
||||
|
||||
}
|
||||
|
||||
public static class CacheHelper
|
||||
|
|
|
|||
|
|
@ -0,0 +1,193 @@
|
|||
using FellowOakDicom;
|
||||
using FellowOakDicom.Media;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper
|
||||
{
|
||||
|
||||
public class StudyDIRInfo
|
||||
{
|
||||
public Guid SubjectId { get; set; }
|
||||
public bool IsTaskStudy { get; set; }
|
||||
|
||||
public Guid SubjectVisitId { get; set; }
|
||||
// Study
|
||||
public Guid DicomStudyId { get; set; }
|
||||
|
||||
public string PatientId { get; set; }
|
||||
public string PatientName { get; set; }
|
||||
public string PatientBirthDate { get; set; }
|
||||
public string PatientSex { get; set; }
|
||||
|
||||
public string StudyInstanceUid { get; set; }
|
||||
public string StudyId { get; set; }
|
||||
public string DicomStudyDate { get; set; }
|
||||
public string DicomStudyTime { get; set; }
|
||||
public string AccessionNumber { get; set; }
|
||||
public string StudyDescription { get; set; }
|
||||
|
||||
// Series
|
||||
public string SeriesInstanceUid { get; set; }
|
||||
public string Modality { get; set; }
|
||||
public string DicomSeriesDate { get; set; }
|
||||
public string DicomSeriesTime { get; set; }
|
||||
public int SeriesNumber { get; set; }
|
||||
public string SeriesDescription { get; set; }
|
||||
|
||||
// Instance
|
||||
public Guid InstanceId { get; set; }
|
||||
public string SopInstanceUid { get; set; }
|
||||
public string SOPClassUID { get; set; }
|
||||
public int InstanceNumber { get; set; }
|
||||
public string MediaStorageSOPClassUID { get; set; }
|
||||
public string MediaStorageSOPInstanceUID { get; set; }
|
||||
public string TransferSytaxUID { get; set; }
|
||||
}
|
||||
public static class DicomDIRHelper
|
||||
{
|
||||
|
||||
public static async Task GenerateStudyDIRAndUploadAsync(List<StudyDIRInfo> list, Dictionary<string, string> dic, string ossFolder, IOSSService _oSSService)
|
||||
{
|
||||
var mappings = new List<string>();
|
||||
int index = 1;
|
||||
|
||||
|
||||
var dicomDir = new DicomDirectory();
|
||||
|
||||
foreach (var item in list.OrderBy(t => t.SeriesNumber).ThenBy(t => t.InstanceNumber))
|
||||
{
|
||||
var dicomUid = DicomUID.Enumerate().FirstOrDefault(uid => uid.UID == item.TransferSytaxUID);
|
||||
|
||||
if (dicomUid != null)
|
||||
{
|
||||
var ts = DicomTransferSyntax.Query(dicomUid);
|
||||
|
||||
var dataset = new DicomDataset(ts)
|
||||
{
|
||||
{ DicomTag.PatientID, item.PatientId ?? string.Empty },
|
||||
{ DicomTag.PatientName, item.PatientName ?? string.Empty },
|
||||
{ DicomTag.PatientBirthDate, item.PatientBirthDate ?? string.Empty },
|
||||
{ DicomTag.PatientSex, item.PatientSex ?? string.Empty },
|
||||
|
||||
{ DicomTag.StudyInstanceUID, item.StudyInstanceUid ?? string.Empty },
|
||||
{ DicomTag.StudyID, item.StudyId ?? string.Empty },
|
||||
{ DicomTag.StudyDate, item.DicomStudyDate ?? string.Empty },
|
||||
{ DicomTag.StudyTime, item.DicomStudyTime ?? string.Empty },
|
||||
{ DicomTag.AccessionNumber, item.AccessionNumber ?? string.Empty },
|
||||
{ DicomTag.StudyDescription, item.StudyDescription ?? string.Empty },
|
||||
|
||||
{ DicomTag.SeriesInstanceUID, item.SeriesInstanceUid ?? string.Empty },
|
||||
{ DicomTag.Modality, item.Modality ?? string.Empty },
|
||||
{ DicomTag.SeriesDate, item.DicomSeriesDate ?? string.Empty },
|
||||
{ DicomTag.SeriesTime, item.DicomSeriesTime ?? string.Empty },
|
||||
{ DicomTag.SeriesNumber, item.SeriesNumber.ToString() ?? string.Empty },
|
||||
{ DicomTag.SeriesDescription, item.SeriesDescription ?? string.Empty },
|
||||
|
||||
{ DicomTag.SOPInstanceUID, item.SopInstanceUid ?? string.Empty },
|
||||
{ DicomTag.SOPClassUID, item.SOPClassUID ?? string.Empty },
|
||||
{ DicomTag.InstanceNumber, item.InstanceNumber.ToString() ?? string.Empty },
|
||||
{ DicomTag.MediaStorageSOPClassUID, item.MediaStorageSOPClassUID ?? string.Empty },
|
||||
{ DicomTag.MediaStorageSOPInstanceUID, item.MediaStorageSOPInstanceUID ?? string.Empty },
|
||||
{ DicomTag.TransferSyntaxUID, item.TransferSytaxUID ?? string.Empty },
|
||||
};
|
||||
|
||||
var dicomFile = new DicomFile(dataset);
|
||||
|
||||
// 文件名递增格式:IM_00001, IM_00002, ...
|
||||
string filename = $@"IMAGE/IM_{index:D5}"; // :D5 表示补足5位
|
||||
|
||||
mappings.Add($"{filename} => {item.InstanceId}");
|
||||
|
||||
dic.Add(item.InstanceId.ToString(), Path.GetFileName(filename));
|
||||
|
||||
dicomDir.AddFile(dicomFile, filename);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//有实际的文件
|
||||
if (mappings.Count > 0)
|
||||
{
|
||||
#region 写入临时路径
|
||||
var tempFilePath = Path.GetTempFileName();
|
||||
|
||||
// 保存 DICOMDIR 到临时文件 不能直接写入到流种
|
||||
await dicomDir.SaveAsync(tempFilePath);
|
||||
|
||||
using (var memoryStream = new MemoryStream(File.ReadAllBytes(tempFilePath)))
|
||||
{
|
||||
// 重置流位置
|
||||
memoryStream.Position = 0;
|
||||
|
||||
await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", false);
|
||||
}
|
||||
|
||||
//清理临时文件
|
||||
File.Delete(tempFilePath);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 映射上传
|
||||
|
||||
// 将映射写入内存流
|
||||
var mappingText = string.Join(Environment.NewLine, mappings);
|
||||
await using var mappingStream = new MemoryStream(Encoding.UTF8.GetBytes(mappingText));
|
||||
|
||||
await _oSSService.UploadToOSSAsync(mappingStream, ossFolder, $"Download_{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}", false);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static StudyDIRInfo ReadDicomDIRInfo(DicomFile dicomFile)
|
||||
{
|
||||
var dataset = dicomFile.Dataset;
|
||||
|
||||
|
||||
var info = new StudyDIRInfo
|
||||
{
|
||||
PatientId = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
|
||||
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
|
||||
PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty),
|
||||
PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty),
|
||||
|
||||
StudyInstanceUid = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty),
|
||||
StudyId = dataset.GetSingleValueOrDefault(DicomTag.StudyID, string.Empty),
|
||||
DicomStudyDate = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty),
|
||||
DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty),
|
||||
AccessionNumber = dataset.GetSingleValueOrDefault(DicomTag.AccessionNumber, string.Empty),
|
||||
StudyDescription = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty),
|
||||
|
||||
SeriesInstanceUid = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty),
|
||||
Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
|
||||
DicomSeriesDate = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty),
|
||||
DicomSeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty),
|
||||
SeriesNumber = dataset.GetSingleValueOrDefault(DicomTag.SeriesNumber, 1),
|
||||
SeriesDescription = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty),
|
||||
|
||||
SopInstanceUid = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty),
|
||||
SOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty),
|
||||
InstanceNumber = dataset.GetSingleValueOrDefault(DicomTag.InstanceNumber, 1),
|
||||
|
||||
MediaStorageSOPClassUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty),
|
||||
MediaStorageSOPInstanceUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty),
|
||||
|
||||
TransferSytaxUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty)
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
namespace IRaCIS.Core.Application.Helper;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper;
|
||||
|
||||
public static class IRCEmailPasswordHelper
|
||||
{
|
||||
|
|
@ -74,4 +77,56 @@ public static class IRCEmailPasswordHelper
|
|||
// 随机打乱密码字符顺序
|
||||
return new string(password.OrderBy(_ => Random.Next()).ToArray());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// https://learn.microsoft.com/zh-cn/dotnet/standard/base-types/how-to-verify-that-strings-are-in-valid-email-format
|
||||
/// <summary>
|
||||
/// 微软官方邮件验证 很宽松
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidEmail(string email)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(email))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Normalize the domain
|
||||
email = Regex.Replace(email, @"(@)(.+)$", DomainMapper,
|
||||
RegexOptions.None, TimeSpan.FromMilliseconds(200));
|
||||
|
||||
// Examines the domain part of the email and normalizes it.
|
||||
string DomainMapper(Match match)
|
||||
{
|
||||
// Use IdnMapping class to convert Unicode domain names.
|
||||
var idn = new IdnMapping();
|
||||
|
||||
// Pull out and process domain name (throws ArgumentException on invalid)
|
||||
string domainName = idn.GetAscii(match.Groups[2].Value);
|
||||
|
||||
return match.Groups[1].Value + domainName;
|
||||
}
|
||||
}
|
||||
catch (RegexMatchTimeoutException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Regex.IsMatch(email,
|
||||
@"^[^@\s]+@[^@\s]+\.[^@\s]+$",
|
||||
RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));
|
||||
}
|
||||
catch (RegexMatchTimeoutException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using MailKit;
|
||||
using MailKit.Security;
|
||||
using MimeKit;
|
||||
using Org.BouncyCastle.Tls;
|
||||
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper;
|
||||
|
|
@ -9,8 +10,17 @@ namespace IRaCIS.Core.Application.Helper;
|
|||
public static class SendEmailHelper
|
||||
{
|
||||
|
||||
public static async Task SendEmailAsync(MimeMessage messageToSend, SystemEmailSendConfig _systemEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
|
||||
public static async Task<string> SendEmailAsync(MimeMessage messageToSend, SystemEmailSendConfig _systemEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
|
||||
{
|
||||
string result = string.Empty;
|
||||
result = messageToSend.MessageId;
|
||||
|
||||
//没有收件人 那么不发送
|
||||
if (messageToSend.To.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var smtp = new MailKit.Net.Smtp.SmtpClient())
|
||||
|
|
@ -36,6 +46,7 @@ public static class SendEmailHelper
|
|||
|
||||
await smtp.DisconnectAsync(true);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -45,7 +56,76 @@ public static class SendEmailHelper
|
|||
throw new Exception(I18n.T("SendEmail_SendFail"), new Exception(ex.Message));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static async Task<string> SendEmailAsync(MimeMessage messageToSend, Trial trial, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
|
||||
{
|
||||
|
||||
// 项目的需要重设 发件地址与邮件地址
|
||||
var fromAddress = messageToSend.From.Mailboxes.FirstOrDefault();
|
||||
if (fromAddress != null)
|
||||
{
|
||||
messageToSend.From.Clear();
|
||||
messageToSend.From.Add(new MailboxAddress(trial.EmailFromName, trial.EmailSMTPServerAddress));
|
||||
}
|
||||
|
||||
|
||||
string result = string.Empty;
|
||||
result = messageToSend.MessageId;
|
||||
|
||||
//没有收件人 那么不发送
|
||||
if (messageToSend.To.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var smtp = new MailKit.Net.Smtp.SmtpClient())
|
||||
{
|
||||
if (messageSentSuccess != null)
|
||||
{
|
||||
smtp.MessageSent += messageSentSuccess;
|
||||
}
|
||||
|
||||
|
||||
smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
|
||||
//await smtp.ConnectAsync("smtp.qq.com", 465, SecureSocketOptions.SslOnConnect);
|
||||
|
||||
//await smtp.AuthenticateAsync("zhou941003@qq.com", "sqfhlpfdvnexbcab");
|
||||
|
||||
|
||||
//await smtp.ConnectAsync(_systemEmailConfig.Host, _systemEmailConfig.Port, SecureSocketOptions.Auto);
|
||||
|
||||
//await smtp.AuthenticateAsync(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode);
|
||||
|
||||
|
||||
await smtp.ConnectAsync(trial.EmailSMTPServerAddress, trial.EmailSMTPServerPort, SecureSocketOptions.Auto);
|
||||
|
||||
await smtp.AuthenticateAsync(trial.EmailFromEmail, trial.EmailAuthorizationCode);
|
||||
|
||||
|
||||
await smtp.SendAsync(messageToSend);
|
||||
|
||||
await smtp.DisconnectAsync(true);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
//---邮件发送失败,您进行的操作未能成功,请检查邮箱或联系维护人员
|
||||
throw new Exception(I18n.T("SendEmail_SendFail"), new Exception(ex.Message));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<bool> TestEmailConfigAsync(SystemEmailSendConfig _systemEmailConfig)
|
||||
|
|
@ -67,7 +147,7 @@ public static class SendEmailHelper
|
|||
return true;
|
||||
}
|
||||
|
||||
public static async Task SendEmailAsync(SMTPEmailConfig sMTPEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
|
||||
public static async Task SendEmailAsync(SMTPEmailConfig sMTPEmailConfig,Trial? trial, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
|
||||
{
|
||||
var messageToSend = new MimeMessage();
|
||||
|
||||
|
|
@ -137,7 +217,7 @@ public static class SendEmailHelper
|
|||
|
||||
await smtp.ConnectAsync(sMTPEmailConfig.Host, sMTPEmailConfig.Port, SecureSocketOptions.Auto);
|
||||
|
||||
await smtp.AuthenticateAsync(sMTPEmailConfig.UserName, sMTPEmailConfig.AuthorizationCode);
|
||||
await smtp.AuthenticateAsync(trial.EmailFromEmail, trial.EmailAuthorizationCode);
|
||||
|
||||
await smtp.SendAsync(messageToSend);
|
||||
|
||||
|
|
|
|||
|
|
@ -60,4 +60,16 @@ namespace IRaCIS.Core.Application.Helper
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||
public class DateTimeTranaslateAttribute : Attribute
|
||||
{
|
||||
public string Formart { get; set; }
|
||||
|
||||
public DateTimeTranaslateAttribute(string formart)
|
||||
{
|
||||
Formart = formart;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using FellowOakDicom.Imaging.LUT;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore.Migrations;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MiniExcelLibs;
|
||||
using MiniExcelLibs.OpenXml;
|
||||
using Newtonsoft.Json;
|
||||
using NPOI.HSSF.UserModel;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using NPOI.SS.UserModel;
|
||||
|
|
@ -243,6 +247,7 @@ public static class ExcelExportHelper
|
|||
|
||||
}
|
||||
|
||||
|
||||
public class DynamicColumnConfig
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -260,7 +265,9 @@ public static class ExcelExportHelper
|
|||
/// </summary>
|
||||
public int TempalteLastColumnIndex { get; set; }
|
||||
|
||||
public List<string> CDISCList { get; set; } = new List<string>();
|
||||
public bool IsCDISCExport { get; set; } = false;
|
||||
|
||||
//public List<string> CDISCList { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 动态的列名 如果Id 重复,那么就按照名称填充,否则就按照Id 填充列数据
|
||||
|
|
@ -300,6 +307,19 @@ public static class ExcelExportHelper
|
|||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public string CDISCCode { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is not ColumItem other) return false;
|
||||
return Id == other.Id && Name == other.Name;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Id, Name);
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> ColumnIdList => ColumnIdNameList == null ? new List<string>() : ColumnIdNameList.Select(t => t.Id.ToString()).ToList();
|
||||
|
|
@ -478,7 +498,9 @@ public static class ExcelExportHelper
|
|||
|
||||
if (dynamicColumnConfig != null)
|
||||
{
|
||||
var isCdics = dynamicColumnConfig.CDISCList.Count > 0;
|
||||
//var isCdics = dynamicColumnConfig.CDISCList.Count > 0;
|
||||
|
||||
var isCdics = dynamicColumnConfig.IsCDISCExport;
|
||||
|
||||
var sheet = workbook.GetSheetAt(0);
|
||||
|
||||
|
|
@ -533,7 +555,7 @@ public static class ExcelExportHelper
|
|||
}
|
||||
|
||||
//创建新的列
|
||||
for (int i = originTotalEndIndex; i < originTotalEndIndex + needAddCount; i++)
|
||||
for (int i = originRemoveEndIndex; i < originRemoveEndIndex + needAddCount; i++)
|
||||
{
|
||||
|
||||
titelRow.CreateCell(i + 1);
|
||||
|
|
@ -554,6 +576,11 @@ public static class ExcelExportHelper
|
|||
titelRow.GetCell(i).SetCellValue(titelRow.GetCell(i - gap).StringCellValue);
|
||||
|
||||
templateRow.GetCell(i).SetCellValue(templateRow.GetCell(i - gap).StringCellValue);
|
||||
|
||||
if (isCdics)
|
||||
{
|
||||
cdicsRow.GetCell(i).SetCellValue(cdicsRow.GetCell(i - gap).StringCellValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -562,6 +589,7 @@ public static class ExcelExportHelper
|
|||
|
||||
for (int i = dynamicColunmStartIndex; i < dynamicColunmStartIndex + needAddCount; i++)
|
||||
{
|
||||
|
||||
var name = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].Name;
|
||||
|
||||
titelRow.GetCell(i).SetCellValue(name);
|
||||
|
|
@ -569,13 +597,15 @@ public static class ExcelExportHelper
|
|||
|
||||
if (isCdics)
|
||||
{
|
||||
var cdicsCode = dynamicColumnConfig.CDISCList[i - dynamicColunmStartIndex];
|
||||
var cdicsCode = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].CDISCCode;
|
||||
|
||||
cdicsRow.GetCell(i).SetCellValue(cdicsCode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
using (var memoryStream2 = new MemoryStream())
|
||||
{
|
||||
workbook.Write(memoryStream2, true);
|
||||
|
|
@ -598,8 +628,11 @@ public static class ExcelExportHelper
|
|||
IgnoreTemplateParameterMissing = true,
|
||||
};
|
||||
|
||||
//await MiniExcel.SaveAsByTemplateAsync("testmini.xlsx", templateStream.ToArray(), translateData);
|
||||
|
||||
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), translateData, config);
|
||||
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
if (dynamicColumnConfig != null)
|
||||
|
|
@ -632,7 +665,7 @@ public static class ExcelExportHelper
|
|||
|
||||
var iteObjDic = itemObj.ToDictionary();
|
||||
|
||||
var itemDicName = iteObjDic[dynamicColumnConfig.DynamicItemDicName]?.ToString();
|
||||
var itemDicName = iteObjDic.ContainsKey(dynamicColumnConfig.DynamicItemDicName) ? iteObjDic[dynamicColumnConfig.DynamicItemDicName]?.ToString() : "";
|
||||
var itemValue = iteObjDic[dynamicColumnConfig.DynamicItemValueName]?.ToString();
|
||||
|
||||
//var writeIndex = itemList.IndexOf(itemObj) + dynamicColumnConfig.AutoColumnStartIndex;
|
||||
|
|
@ -652,9 +685,45 @@ public static class ExcelExportHelper
|
|||
if (itemDicName.IsNotNullOrEmpty())
|
||||
{
|
||||
|
||||
var translatedItemData = dynamicTranslateDataList[itemDicName].Where(t => t.Code.ToLower() == itemValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
var optionTypeEnumStr = iteObjDic.ContainsKey("OptionTypeEnum") ? iteObjDic["OptionTypeEnum"]?.ToString() : "0";
|
||||
|
||||
var translatedItemData = "";
|
||||
//多选
|
||||
if (optionTypeEnumStr == "1")
|
||||
{
|
||||
int[] enumValues = new int[0];
|
||||
// 1. 反序列化 JSON 数组 (字符串枚举)
|
||||
if (!itemValue.StartsWith("[") || !itemValue.EndsWith("]"))
|
||||
{
|
||||
enumValues = new int[1] { int.Parse(itemValue) };
|
||||
}
|
||||
else
|
||||
{
|
||||
enumValues = JsonConvert.DeserializeObject<int[]>(itemValue)?? new int[0];
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 2. 翻译每一项并输出逗号拼接字符串
|
||||
translatedItemData = string.Join(",",
|
||||
enumValues.Select(code =>
|
||||
dynamicTranslateDataList[itemDicName]
|
||||
.FirstOrDefault(t =>
|
||||
string.Equals(code.ToString(),t.Code, StringComparison.OrdinalIgnoreCase)
|
||||
) is var r && r != null
|
||||
? (isEn_US ? r.Value : r.ValueCN)
|
||||
: string.Empty
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
translatedItemData = dynamicTranslateDataList[itemDicName].Where(t => t.Code.ToLower() == itemValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
|
||||
}
|
||||
|
||||
row.GetCell(writeIndex).SetCellValue(translatedItemData);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -896,7 +965,7 @@ public static class ExcelExportHelper
|
|||
{
|
||||
var name = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].Name;
|
||||
|
||||
var cdicsCode = dynamicColumnConfig.CDISCList[i - dynamicColunmStartIndex];
|
||||
var cdicsCode = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].CDISCCode;
|
||||
|
||||
cdicsRow.GetCell(i).SetCellValue(cdicsCode);
|
||||
titelRow.GetCell(i).SetCellValue(name);
|
||||
|
|
@ -1009,6 +1078,34 @@ public static class ExcelExportHelper
|
|||
|
||||
|
||||
|
||||
public static async Task<IActionResult> MutiSheetDataExportAsync(string code, object data, string exportFileNamePrefix, IRepository<CommonDocument> _commonDocumentRepository, IWebHostEnvironment _hostEnvironment)
|
||||
{
|
||||
|
||||
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code);
|
||||
|
||||
|
||||
//模板路径
|
||||
var tplPath = physicalPath;
|
||||
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
var config = new OpenXmlConfiguration()
|
||||
{
|
||||
IgnoreTemplateParameterMissing = true,
|
||||
};
|
||||
|
||||
await MiniExcel.SaveAsByTemplateAsync(memoryStream, tplPath, data, config);
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
|
||||
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
{
|
||||
FileDownloadName = $"{(string.IsNullOrEmpty(exportFileNamePrefix) ? "" : exportFileNamePrefix + "_")}{Path.GetFileNameWithoutExtension(fileName)}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出文件模板
|
||||
/// </summary>
|
||||
|
|
@ -1090,4 +1187,7 @@ public static class ExcelExportHelper
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -69,6 +69,15 @@ public static class FileStoreHelper
|
|||
return rootFolder;
|
||||
}
|
||||
|
||||
public static string GetDonwnloadImageFolder(IWebHostEnvironment _hostEnvironment)
|
||||
{
|
||||
var rootPath = GetIRaCISRootPath(_hostEnvironment);
|
||||
|
||||
var rootFolder = Path.Combine(rootPath, StaticData.Folder.DownloadIamgeFolder);
|
||||
|
||||
return rootFolder;
|
||||
}
|
||||
|
||||
//根据相对路径 获取具体文件物理地址
|
||||
public static string GetPhysicalFilePath(IWebHostEnvironment _hostEnvironment, string relativePath)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ namespace IRaCIS.Core.Application.Helper
|
|||
//添加 或者更新定时任务 Id 要唯一标识一个定义任务
|
||||
public static void AddOrUpdateCronJob<T>(string jobId, Expression<Action<T>> methodCall, string cron, string queueName = "default")
|
||||
{
|
||||
RecurringJob.AddOrUpdate<T>(jobId, queueName, methodCall, cron);
|
||||
RecurringJob.AddOrUpdate<T>(jobId, queueName, methodCall, cron, new RecurringJobOptions() { TimeZone = TimeZoneInfo.Local });
|
||||
}
|
||||
|
||||
public static void AddOrUpdateInitCronJob<T>(string jobId, Expression<Action<T>> methodCall, string cron)
|
||||
{
|
||||
RecurringJob.AddOrUpdate<T>(jobId, "sys_init", methodCall, cron);
|
||||
RecurringJob.AddOrUpdate<T>(jobId, "sys_init", methodCall, cron, new RecurringJobOptions() { TimeZone = TimeZoneInfo.Local });
|
||||
}
|
||||
|
||||
public static void RemoveCronJob(string jobId)
|
||||
|
|
@ -77,13 +77,32 @@ namespace IRaCIS.Core.Application.Helper
|
|||
case EmailBusinessScenario.ExpeditedReading:
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new UrgentIRUnReadTaskRecurringEvent() { TrialId = trialId }, default), emailCron);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void AddOrUpdateTimingCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron)
|
||||
{
|
||||
switch (businessScenario)
|
||||
{
|
||||
|
||||
case EmailBusinessScenario.GeneralTraining_ExpirationNotification:
|
||||
|
||||
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new SystemDocumentErverDayEvent() { }, default), emailCron);
|
||||
break;
|
||||
case EmailBusinessScenario.TrialTraining_ExpirationNotification:
|
||||
|
||||
Console.WriteLine("更新项目到期job");
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new TrialDocumentErverDayEvent() { }, default), emailCron);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
using IRaCIS.Core.Domain.Models;
|
||||
using MaxMind.GeoIP2;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper
|
||||
{
|
||||
public static class IPCityHelper
|
||||
{
|
||||
|
||||
public static string GetCityResponse(string ip)
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, StaticData.Folder.Resources, "GeoLite2-City.mmdb");
|
||||
|
||||
try
|
||||
{
|
||||
using (var reader = new DatabaseReader(path))
|
||||
{
|
||||
|
||||
var city = reader.City(ip);
|
||||
|
||||
//Console.WriteLine(city.Country.IsoCode); // 'US' 'CN'
|
||||
//Console.WriteLine(city.Country.Name); // 'United States' 'China'
|
||||
////Console.WriteLine(city.Country.Names["zh-CN"]); // '美国'
|
||||
//Console.WriteLine(city.MostSpecificSubdivision.Name); // 'Minnesota' 'Hubei'
|
||||
//Console.WriteLine(city.City.Name); // 'Minneapolis' 'WUHan'
|
||||
|
||||
return $"{city.Country.Name} | {city.MostSpecificSubdivision.Name} | {city.City.Name}";
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return $"UN | UN | {ip}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using AlibabaCloud.SDK.Sts20150401;
|
||||
using Aliyun.OSS;
|
||||
using Aliyun.OSS.Common;
|
||||
using Amazon;
|
||||
using Amazon.Runtime;
|
||||
using Amazon.S3;
|
||||
|
|
@ -9,11 +10,15 @@ using Amazon.SecurityToken.Model;
|
|||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.NewtonsoftJson;
|
||||
using MassTransit;
|
||||
using MassTransit.Caching.Internals;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Minio;
|
||||
using Minio.DataModel;
|
||||
using Minio.DataModel.Args;
|
||||
using Minio.Exceptions;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Web;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper;
|
||||
|
||||
|
|
@ -59,7 +64,7 @@ public class AliyunOSSOptions
|
|||
|
||||
public int DurationSeconds { get; set; }
|
||||
|
||||
|
||||
public string PreviewEndpoint { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +110,9 @@ public class AliyunOSSTempToken
|
|||
public string SecurityToken { get; set; }
|
||||
public DateTime Expiration { get; set; }
|
||||
|
||||
public string PreviewEndpoint { get; set; }
|
||||
|
||||
public string DownloadEndPoint => EndPoint.Insert(EndPoint.IndexOf("//") + 2, BucketName + ".");
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +126,7 @@ public class AWSTempToken
|
|||
public string SecretAccessKey { get; set; }
|
||||
public string BucketName { get; set; }
|
||||
public string ViewEndpoint { get; set; }
|
||||
public DateTime Expiration { get; set; }
|
||||
public DateTime? Expiration { get; set; }
|
||||
}
|
||||
|
||||
public enum ObjectStoreUse
|
||||
|
|
@ -135,19 +143,27 @@ public enum ObjectStoreUse
|
|||
public interface IOSSService
|
||||
{
|
||||
public Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true);
|
||||
public Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true);
|
||||
public Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true, bool randomFileName = false);
|
||||
|
||||
public Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath);
|
||||
|
||||
public Task<Stream> GetStreamFromOSSAsync(string ossRelativePath);
|
||||
|
||||
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
|
||||
|
||||
public Task<string> GetSignedUrl(string ossRelativePath);
|
||||
|
||||
public Task DeleteFromPrefix(string prefix);
|
||||
public Task DeleteFromPrefix(string prefix, bool isCache = false);
|
||||
|
||||
public Task DeleteObjects(List<string> objectKeys);
|
||||
|
||||
List<string> GetRootFolderNames();
|
||||
|
||||
public ObjectStoreDTO GetObjectStoreTempToken();
|
||||
|
||||
public Task MoveObject(string sourcePath, string destPath, bool overwrite = true);
|
||||
|
||||
public Task<long> GetObjectSizeAsync(string sourcePath);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -178,7 +194,7 @@ public class OSSService : IOSSService
|
|||
/// <returns></returns>
|
||||
public async Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
BackBatchGetToken();
|
||||
|
||||
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{fileRealName}" : $"{oosFolderPath}/{fileRealName}";
|
||||
|
||||
|
|
@ -268,6 +284,37 @@ public class OSSService : IOSSService
|
|||
}
|
||||
|
||||
|
||||
//后端批量上传 或者下载,不每个文件获取临时token
|
||||
private void BackBatchGetToken()
|
||||
{
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
if (AliyunOSSTempToken == null)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
}
|
||||
//token 过期了
|
||||
if (AliyunOSSTempToken?.Expiration.AddSeconds(10) <= DateTime.Now)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
if (AWSTempToken == null)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
}
|
||||
//token 过期了
|
||||
if (AWSTempToken.Expiration?.AddSeconds(10) <= DateTime.Now)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
|
||||
|
|
@ -275,16 +322,22 @@ public class OSSService : IOSSService
|
|||
/// <param name="localFilePath"></param>
|
||||
/// <param name="oosFolderPath"></param>
|
||||
/// <param name="isFileNameAddGuid"></param>
|
||||
/// <param name="randomFileName">随机文件名</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BusinessValidationFailedException"></exception>
|
||||
public async Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true)
|
||||
public async Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true, bool randomFileName = false)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
BackBatchGetToken();
|
||||
|
||||
var localFileName = Path.GetFileName(localFilePath);
|
||||
|
||||
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{localFileName}" : $"{oosFolderPath}/{localFileName}";
|
||||
|
||||
if (randomFileName)
|
||||
{
|
||||
var fileExtension = localFileName.Split(".").LastOrDefault();
|
||||
ossRelativePath = $"{oosFolderPath}/{Guid.NewGuid()}.{fileExtension}";
|
||||
}
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
|
|
@ -349,7 +402,7 @@ public class OSSService : IOSSService
|
|||
|
||||
public async Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
BackBatchGetToken();
|
||||
|
||||
ossRelativePath = ossRelativePath.TrimStart('/');
|
||||
try
|
||||
|
|
@ -362,14 +415,12 @@ public class OSSService : IOSSService
|
|||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
||||
// 上传文件
|
||||
var result = _ossClient.GetObject(aliConfig.BucketName, ossRelativePath);
|
||||
|
||||
// 将下载的文件流保存到本地文件
|
||||
using (var fs = File.OpenWrite(localFilePath))
|
||||
{
|
||||
result.Content.CopyTo(fs);
|
||||
fs.Close();
|
||||
await result.Content.CopyToAsync(fs);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -433,6 +484,96 @@ public class OSSService : IOSSService
|
|||
|
||||
}
|
||||
|
||||
public async Task<Stream> GetStreamFromOSSAsync(string ossRelativePath)
|
||||
{
|
||||
BackBatchGetToken();
|
||||
ossRelativePath = ossRelativePath.TrimStart('/');
|
||||
|
||||
try
|
||||
{
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
|
||||
var _ossClient = new OssClient(
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
|
||||
AliyunOSSTempToken.AccessKeyId,
|
||||
AliyunOSSTempToken.AccessKeySecret,
|
||||
AliyunOSSTempToken.SecurityToken
|
||||
);
|
||||
|
||||
var result = _ossClient.GetObject(aliConfig.BucketName, ossRelativePath);
|
||||
|
||||
// 将OSS返回的流复制到内存流中并返回
|
||||
var memoryStream = new MemoryStream();
|
||||
await result.Content.CopyToAsync(memoryStream);
|
||||
memoryStream.Position = 0; // 重置位置以便读取
|
||||
return memoryStream;
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
|
||||
{
|
||||
var minIOConfig = ObjectStoreServiceOptions.MinIO;
|
||||
|
||||
var minioClient = new MinioClient()
|
||||
.WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
|
||||
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey)
|
||||
.WithSSL(minIOConfig.UseSSL)
|
||||
.Build();
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
var getObjectArgs = new GetObjectArgs()
|
||||
.WithBucket(minIOConfig.BucketName)
|
||||
.WithObject(ossRelativePath)
|
||||
.WithCallbackStream(stream => stream.CopyToAsync(memoryStream));
|
||||
|
||||
await minioClient.GetObjectAsync(getObjectArgs);
|
||||
memoryStream.Position = 0;
|
||||
return memoryStream;
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
var credentials = new SessionAWSCredentials(
|
||||
AWSTempToken.AccessKeyId,
|
||||
AWSTempToken.SecretAccessKey,
|
||||
AWSTempToken.SessionToken
|
||||
);
|
||||
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
var getObjectRequest = new Amazon.S3.Model.GetObjectRequest
|
||||
{
|
||||
BucketName = awsConfig.BucketName,
|
||||
Key = ossRelativePath
|
||||
};
|
||||
|
||||
var response = await amazonS3Client.GetObjectAsync(getObjectRequest);
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
await response.ResponseStream.CopyToAsync(memoryStream);
|
||||
memoryStream.Position = 0;
|
||||
return memoryStream;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new BusinessValidationFailedException("oss流获取失败! " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<string> GetSignedUrl(string ossRelativePath)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
|
|
@ -528,6 +669,185 @@ public class OSSService : IOSSService
|
|||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 移动OSS文件到新路径
|
||||
/// </summary>
|
||||
/// <param name="sourcePath">原文件路径(格式:bucket/key)</param>
|
||||
/// <param name="destPath">新文件路径(格式:bucket/key)</param>
|
||||
/// <param name="overwrite">是否覆盖已存在的目标文件(默认true)</param>
|
||||
public async Task MoveObject(string sourcePath, string destPath, bool overwrite = true)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
|
||||
switch (ObjectStoreServiceOptions.ObjectStoreUse)
|
||||
{
|
||||
case "AliyunOSS":
|
||||
#region 阿里云
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
var client = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
if (sourcePath.StartsWith("/"))
|
||||
{
|
||||
sourcePath = sourcePath.Substring(1);
|
||||
}
|
||||
|
||||
|
||||
if (destPath.StartsWith("/"))
|
||||
{
|
||||
destPath = destPath.Substring(1);
|
||||
}
|
||||
|
||||
var sourceBucket = aliConfig.BucketName;
|
||||
var sourceKey = sourcePath;
|
||||
|
||||
var destBucket = aliConfig.BucketName;
|
||||
|
||||
var destKey = destPath;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// 检查目标是否存在(当不允许覆盖时)
|
||||
if (!overwrite && client.DoesObjectExist(destBucket, destKey))
|
||||
{
|
||||
throw new InvalidOperationException("File Exist");
|
||||
}
|
||||
|
||||
|
||||
//var copyRequest = new Aliyun.OSS.CopyObjectRequest(sourceBucket, sourceKey, sourceBucket, destKey);
|
||||
//var result = client.CopyObject(copyRequest);
|
||||
|
||||
//// 2. 删除原文件(可选,根据是否需要保留原文件)
|
||||
//client.DeleteObject(sourceBucket, sourceKey);
|
||||
|
||||
// 执行复制
|
||||
var copyRequestAli = new Aliyun.OSS.CopyObjectRequest(
|
||||
sourceBucket, sourceKey,
|
||||
destBucket, destKey);
|
||||
|
||||
// 保持原文件元数据
|
||||
copyRequestAli.NewObjectMetadata = new ObjectMetadata
|
||||
{
|
||||
ContentType = client.GetObjectMetadata(sourceBucket, sourceKey).ContentType
|
||||
};
|
||||
|
||||
var result = client.CopyObject(copyRequestAli);
|
||||
|
||||
// 删除原文件(仅在复制成功后)
|
||||
client.DeleteObject(sourceBucket, sourceKey);
|
||||
}
|
||||
catch (OssException ex)
|
||||
{
|
||||
throw new Exception($"[{ex.ErrorCode}] {ex.Message}", ex);
|
||||
}
|
||||
#endregion
|
||||
|
||||
break;
|
||||
case "MinIO":
|
||||
#region MinIO
|
||||
var minIOConfig = ObjectStoreServiceOptions.MinIO;
|
||||
var minioClient = new MinioClient()
|
||||
.WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
|
||||
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey)
|
||||
.WithSSL(minIOConfig.UseSSL)
|
||||
.Build();
|
||||
|
||||
// 定义源路径和目标路径
|
||||
string destinationKey = "b路径/文件名";
|
||||
|
||||
try
|
||||
{
|
||||
// 1. 复制文件到新路径[2,5](@ref)
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
// 下载源文件流
|
||||
await minioClient.GetObjectAsync(new GetObjectArgs()
|
||||
.WithBucket(minIOConfig.BucketName)
|
||||
.WithObject(sourcePath)
|
||||
.WithCallbackStream(stream => stream.CopyTo(memoryStream)));
|
||||
|
||||
memoryStream.Position = 0; // 重置流位置
|
||||
|
||||
// 上传到新路径
|
||||
await minioClient.PutObjectAsync(new PutObjectArgs()
|
||||
.WithBucket(minIOConfig.BucketName)
|
||||
.WithObject(destinationKey)
|
||||
.WithStreamData(memoryStream)
|
||||
.WithObjectSize(memoryStream.Length));
|
||||
}
|
||||
|
||||
// 2. 删除原文件[1,6](@ref)
|
||||
await minioClient.RemoveObjectAsync(new RemoveObjectArgs()
|
||||
.WithBucket(minIOConfig.BucketName)
|
||||
.WithObject(sourcePath));
|
||||
}
|
||||
catch (MinioException ex)
|
||||
{
|
||||
// 处理异常(例如:记录日志或抛出)
|
||||
throw new Exception();
|
||||
}
|
||||
#endregion
|
||||
|
||||
break;
|
||||
case "AWS":
|
||||
#region AWS
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
var credentials = new SessionAWSCredentials(
|
||||
AWSTempToken.AccessKeyId,
|
||||
AWSTempToken.SecretAccessKey,
|
||||
AWSTempToken.SessionToken
|
||||
);
|
||||
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
// 定义原路径和目标路径
|
||||
|
||||
|
||||
// 1. 复制对象到新路径
|
||||
var copyRequest = new Amazon.S3.Model.CopyObjectRequest
|
||||
{
|
||||
SourceBucket = awsConfig.BucketName,
|
||||
SourceKey = sourcePath,
|
||||
DestinationBucket = awsConfig.BucketName,
|
||||
DestinationKey = destPath
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// 执行复制操作
|
||||
await amazonS3Client.CopyObjectAsync(copyRequest);
|
||||
|
||||
// 2. 删除原对象
|
||||
var deleteRequest = new Amazon.S3.Model.DeleteObjectRequest
|
||||
{
|
||||
BucketName = awsConfig.BucketName,
|
||||
Key = sourcePath
|
||||
};
|
||||
await amazonS3Client.DeleteObjectAsync(deleteRequest);
|
||||
|
||||
|
||||
}
|
||||
catch (AmazonS3Exception ex)
|
||||
{
|
||||
Console.WriteLine($"ERROR: {ex.Message}");
|
||||
// 可根据异常类型细化处理(如文件不存在、权限问题等)
|
||||
}
|
||||
#endregion
|
||||
break;
|
||||
default:
|
||||
throw new BusinessValidationFailedException("ERROR");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有根目录名称
|
||||
/// </summary>
|
||||
|
|
@ -582,7 +902,7 @@ public class OSSService : IOSSService
|
|||
/// </summary>
|
||||
/// <param name="prefix"></param>
|
||||
/// <returns></returns>
|
||||
public async Task DeleteFromPrefix(string prefix)
|
||||
public async Task DeleteFromPrefix(string prefix, bool isCache = false)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
|
||||
|
|
@ -592,6 +912,21 @@ public class OSSService : IOSSService
|
|||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
||||
var bucketName = string.Empty;
|
||||
|
||||
if (isCache)
|
||||
{
|
||||
Uri uri = new Uri(aliConfig.ViewEndpoint);
|
||||
string host = uri.Host; // 获取 "zy-irc-test-dev-cache.oss-cn-shanghai.aliyuncs.com"
|
||||
string[] parts = host.Split('.');
|
||||
bucketName = parts[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
bucketName = aliConfig.BucketName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -600,7 +935,7 @@ public class OSSService : IOSSService
|
|||
do
|
||||
{
|
||||
// 使用 prefix 模拟目录结构,设置 MaxKeys 和 NextMarker
|
||||
objectListing = _ossClient.ListObjects(new Aliyun.OSS.ListObjectsRequest(aliConfig.BucketName)
|
||||
objectListing = _ossClient.ListObjects(new Aliyun.OSS.ListObjectsRequest(bucketName)
|
||||
{
|
||||
Prefix = prefix,
|
||||
MaxKeys = 1000,
|
||||
|
|
@ -612,7 +947,7 @@ public class OSSService : IOSSService
|
|||
// 删除获取到的文件
|
||||
if (keys.Count > 0)
|
||||
{
|
||||
_ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(aliConfig.BucketName, keys, false));
|
||||
_ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(bucketName, keys, false));
|
||||
}
|
||||
|
||||
// 设置 NextMarker 以获取下一页的数据
|
||||
|
|
@ -669,7 +1004,6 @@ public class OSSService : IOSSService
|
|||
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
|
||||
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
|
||||
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
|
||||
|
||||
|
|
@ -721,6 +1055,155 @@ public class OSSService : IOSSService
|
|||
}
|
||||
}
|
||||
|
||||
public async Task DeleteObjects(List<string> objectKeys)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
||||
if (objectKeys.Count > 0)
|
||||
{
|
||||
var result = _ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(aliConfig.BucketName, objectKeys, false));
|
||||
|
||||
}
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
|
||||
{
|
||||
var minIOConfig = ObjectStoreServiceOptions.MinIO;
|
||||
|
||||
|
||||
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
|
||||
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
|
||||
.Build();
|
||||
|
||||
|
||||
if (objectKeys.Count > 0)
|
||||
{
|
||||
var objArgs = new RemoveObjectsArgs()
|
||||
.WithBucket(minIOConfig.BucketName)
|
||||
.WithObjects(objectKeys);
|
||||
|
||||
// 删除对象
|
||||
await minioClient.RemoveObjectsAsync(objArgs);
|
||||
}
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
|
||||
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
|
||||
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
|
||||
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
if (objectKeys.Count > 0)
|
||||
{
|
||||
// 准备删除请求
|
||||
var deleteObjectsRequest = new Amazon.S3.Model.DeleteObjectsRequest
|
||||
{
|
||||
BucketName = awsConfig.BucketName,
|
||||
Objects = objectKeys.Select(t => new KeyVersion() { Key = t }).ToList()
|
||||
};
|
||||
|
||||
|
||||
// 批量删除对象
|
||||
var deleteObjectsResponse = await amazonS3Client.DeleteObjectsAsync(deleteObjectsRequest);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<long> GetObjectSizeAsync(string sourcePath)
|
||||
{
|
||||
BackBatchGetToken();
|
||||
|
||||
|
||||
var objectkey = sourcePath.Trim('/');
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
||||
var key = HttpUtility.UrlDecode(objectkey);
|
||||
var metadata = _ossClient.GetObjectMetadata(aliConfig.BucketName, key);
|
||||
|
||||
long fileSize = metadata?.ContentLength ?? 0; // 文件大小(字节)
|
||||
|
||||
return fileSize;
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
|
||||
{
|
||||
var minIOConfig = ObjectStoreServiceOptions.MinIO;
|
||||
|
||||
|
||||
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
|
||||
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
|
||||
.Build();
|
||||
|
||||
|
||||
var stat = await minioClient.StatObjectAsync(new Minio.DataModel.Args.StatObjectArgs()
|
||||
.WithBucket(minIOConfig.BucketName)
|
||||
.WithObject(objectkey));
|
||||
|
||||
return stat.Size; // 文件大小(字节)
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
|
||||
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
|
||||
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
|
||||
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var request = new Amazon.S3.Model.GetObjectMetadataRequest
|
||||
{
|
||||
BucketName = awsConfig.BucketName,
|
||||
Key = objectkey
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
var response = await amazonS3Client.GetObjectMetadataAsync(request);
|
||||
|
||||
long fileSize = response.ContentLength; // 文件大小(字节)
|
||||
|
||||
return fileSize;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ObjectStoreDTO GetObjectStoreTempToken()
|
||||
{
|
||||
|
|
@ -764,6 +1247,7 @@ public class OSSService : IOSSService
|
|||
BucketName = ossOptions.BucketName,
|
||||
EndPoint = ossOptions.EndPoint,
|
||||
ViewEndpoint = ossOptions.ViewEndpoint,
|
||||
PreviewEndpoint = ossOptions.PreviewEndpoint
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -15,14 +15,20 @@ public static class ImageHelper
|
|||
|
||||
fileStorePath = fileStorePath ?? filePath + ".preview.jpeg";
|
||||
|
||||
using (var image = SixLabors.ImageSharp.Image.Load(filePath))
|
||||
{
|
||||
// 读取 DICOM 文件
|
||||
var dicomImage = new DicomImage(filePath);
|
||||
|
||||
// 渲染 DICOM 图像到 ImageSharp 格式
|
||||
using (var image = dicomImage.RenderImage().AsSharpImage())
|
||||
{
|
||||
// 生成缩略图(调整大小)
|
||||
image.Mutate(x => x.Resize(500, 500));
|
||||
|
||||
image.Save(fileStorePath);
|
||||
|
||||
// 保存缩略图为 JPEG
|
||||
image.Save(fileStorePath, new JpegEncoder());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper
|
||||
{
|
||||
public static class SafeBussinessHelper
|
||||
{
|
||||
public static async Task<bool> RunAsync(Func<Task> func, [CallerMemberName] string caller = "", string errorMsgTitle = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
await func();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error($"【{errorMsgTitle}失败 - {caller}】: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<(bool Success, T? Result)> RunAsync<T>(Func<Task<T>> func, [CallerMemberName] string caller = "", string errorMsgTitle = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await func();
|
||||
return (true, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error($"【{errorMsgTitle}失败 - {caller}】: {ex.Message}");
|
||||
return (false, default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -33,37 +33,37 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IdentityModel.OidcClient" Version="6.0.0" />
|
||||
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.4" />
|
||||
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.400.36" />
|
||||
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.6" />
|
||||
<PackageReference Include="AWSSDK.SecurityToken" Version="4.0.1.3" />
|
||||
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.405" />
|
||||
<PackageReference Include="DocX" Version="3.0.1" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="4.0.4.1" />
|
||||
<PackageReference Include="DocX" Version="4.0.25105.5786" />
|
||||
<PackageReference Include="FreeSpire.Doc" Version="12.2.0" />
|
||||
<PackageReference Include="Hangfire.Core" Version="1.8.14" />
|
||||
<PackageReference Include="ExcelDataReader" Version="3.7.0" />
|
||||
<PackageReference Include="ExcelDataReader.DataSet" Version="3.7.0" />
|
||||
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
|
||||
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.5" />
|
||||
<PackageReference Include="fo-dicom" Version="5.1.3" />
|
||||
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.1.3" />
|
||||
<PackageReference Include="fo-dicom.Codecs" Version="5.15.1" />
|
||||
<PackageReference Include="DistributedLock.Redis" Version="1.1.0" />
|
||||
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" />
|
||||
<PackageReference Include="fo-dicom" Version="5.2.2" />
|
||||
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.2" />
|
||||
<PackageReference Include="fo-dicom.Codecs" Version="5.16.4" />
|
||||
<PackageReference Include="IP2Region.Net" Version="2.0.2" />
|
||||
<PackageReference Include="MailKit" Version="4.7.1.1" />
|
||||
<PackageReference Include="MailKit" Version="4.11.0" />
|
||||
<PackageReference Include="Masa.Contrib.Service.MinimalAPIs" Version="1.0.0" />
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
|
||||
<PackageReference Include="MimeKit" Version="4.7.1" />
|
||||
<PackageReference Include="MiniExcel" Version="1.34.2" />
|
||||
<PackageReference Include="MimeKit" Version="4.11.0" />
|
||||
<PackageReference Include="MiniExcel" Version="1.41.2" />
|
||||
<PackageReference Include="Minio" Version="6.0.3" />
|
||||
<PackageReference Include="MiniWord" Version="0.8.0" />
|
||||
<PackageReference Include="MiniWord" Version="0.9.2" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
|
||||
<TreatAsUsed>true</TreatAsUsed>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NPOI" Version="2.7.1" />
|
||||
<PackageReference Include="NPOI" Version="2.7.4" />
|
||||
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
|
||||
<PackageReference Include="RestSharp" Version="112.1.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.2" />
|
||||
<PackageReference Include="ZiggyCreatures.FusionCache" Version="1.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -74,4 +74,8 @@
|
|||
<Folder Include="Service\MinimalApiService\CodeTemplate\FrontTemplate\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Service\TrialSiteUser\TrialStatService.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,8 +1,21 @@
|
|||
using MiniExcelLibs.Attributes;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using MiniExcelLibs.Attributes;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace IRaCIS.Core.Application.MassTransit.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// 全量一致性核查
|
||||
/// </summary>
|
||||
public record ConsistenFullCheckCommand
|
||||
{
|
||||
public List<CheckViewModel> ETCList { get; set; } = new List<CheckViewModel>();
|
||||
|
||||
public Guid TrialId { get; set; }
|
||||
|
||||
public Guid InspectionFileId { get; set; }
|
||||
}
|
||||
|
||||
public record ConsistenCheckCommand
|
||||
{
|
||||
public List<CheckViewModel> ETCList { get; set; } = new List<CheckViewModel>();
|
||||
|
|
@ -23,7 +36,18 @@ namespace IRaCIS.Core.Application.MassTransit.Command
|
|||
public Guid StudyId { get; set; }
|
||||
}
|
||||
|
||||
public class FullCheckResult: CheckViewModel
|
||||
{
|
||||
public string LatestScanDateStr { get; set; } = string.Empty;
|
||||
public string Modalitys { get; set; } = string.Empty;
|
||||
public DateTime CheckTime { get; set; }
|
||||
|
||||
public string CheckResult { get; set; }
|
||||
|
||||
|
||||
[DictionaryTranslateAttribute("CheckState")]
|
||||
public CheckStateEnum CheckState { get; set; }
|
||||
}
|
||||
|
||||
|
||||
//[ExcelImporter(/*ImportResultFilter = typeof(ImportResultFilteTest),*/ IsLabelingError = true)]
|
||||
|
|
@ -62,8 +86,13 @@ namespace IRaCIS.Core.Application.MassTransit.Command
|
|||
[ExcelColumnName("Modality")]
|
||||
public string Modality { get; set; } = string.Empty;
|
||||
|
||||
#region 全量一致性核查加入
|
||||
[DictionaryTranslateAttribute("Subject_Visit_Status")]
|
||||
public SubjectStatus SubjectStatus { get; set; }
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,19 +14,20 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer;
|
|||
|
||||
public static class CommonEmailHelper
|
||||
{
|
||||
public static async Task<EmailNoticeConfig> GetEmailSubejctAndHtmlInfoAndBuildAsync(IRepository<EmailNoticeConfig> _emailNoticeConfigrepository, EmailBusinessScenario scenario, MimeMessage messageToSend,
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailFunc)
|
||||
|
||||
/// <summary>
|
||||
/// 系统默认邮件 + 项目默认邮件 (不用添加到项目邮件配置中,才发送)
|
||||
/// </summary>
|
||||
/// <param name="configInfo"></param>
|
||||
/// <param name="messageToSend"></param>
|
||||
/// <param name="emailFunc"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BusinessValidationFailedException"></exception>
|
||||
public static async Task<EmailNoticeConfig> GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailNoticeConfig configInfo, MimeMessage messageToSend,
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailFunc)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
var configInfo = await _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario).FirstOrDefaultAsync();
|
||||
|
||||
if (configInfo == null)
|
||||
{
|
||||
throw new BusinessValidationFailedException("系统未找到当前场景邮件配置信息,请联系运维人员核查");
|
||||
}
|
||||
|
||||
|
||||
var (topicStr, htmlBodyStr) = isEn_US ? (configInfo.EmailTopic, configInfo.EmailHtmlContent) : (configInfo.EmailTopicCN, configInfo.EmailHtmlContentCN);
|
||||
|
||||
try
|
||||
|
|
@ -36,8 +37,9 @@ public static class CommonEmailHelper
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//"邮件模板内容有误,填充内容出现问题,请联系运维人员核查"
|
||||
|
||||
throw new BusinessValidationFailedException("邮件模板内容有误,填充内容出现问题,请联系运维人员核查");
|
||||
throw new BusinessValidationFailedException(I18n.T("CommonEmail_ConfigError"));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -52,6 +54,49 @@ public static class CommonEmailHelper
|
|||
return configInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 项目手动邮件 (需要添加到项目邮件配置中,才发送)
|
||||
/// </summary>
|
||||
/// <param name="configInfo"></param>
|
||||
/// <param name="messageToSend"></param>
|
||||
/// <param name="emailFunc"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BusinessValidationFailedException"></exception>
|
||||
public static async Task<TrialEmailNoticeConfig> GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(TrialEmailNoticeConfig configInfo, MimeMessage messageToSend,
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailFunc)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
|
||||
var (topicStr, htmlBodyStr) = isEn_US ? (configInfo.EmailTopic, configInfo.EmailHtmlContent) : (configInfo.EmailTopicCN, configInfo.EmailHtmlContentCN);
|
||||
|
||||
try
|
||||
{
|
||||
//每个场景修改主题 和body的逻辑不一样
|
||||
(topicStr, htmlBodyStr) = emailFunc((topicStr, htmlBodyStr));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//"邮件模板内容有误,填充内容出现问题,请联系运维人员核查"
|
||||
|
||||
throw new BusinessValidationFailedException(I18n.T("CommonEmail_ConfigError"));
|
||||
}
|
||||
|
||||
|
||||
messageToSend.Subject = topicStr;
|
||||
|
||||
var builder = new BodyBuilder();
|
||||
|
||||
builder.HtmlBody = htmlBodyStr;
|
||||
|
||||
messageToSend.Body = builder.ToMessageBody();
|
||||
|
||||
return configInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static string ReplaceCompanyName(SystemEmailSendConfig _systemEmailConfig, string needDealtxt)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
|
@ -74,12 +119,13 @@ public static class CommonEmailHelper
|
|||
var enumValueList = inDto.DictionaryList.Select(x => x.EnumValue).ToList();
|
||||
|
||||
|
||||
var dicList =await inDto.DictionaryRepository.Where(x => dictionaryCodelist.Contains(x.Parent.Code) && enumValueList.Contains(x.Code)).Select(x=>new DictionaryData() {
|
||||
var dicList = await inDto.DictionaryRepository.Where(x => dictionaryCodelist.Contains(x.Parent.Code) && enumValueList.Contains(x.Code)).Select(x => new DictionaryData()
|
||||
{
|
||||
|
||||
DictionaryCode=x.Parent.Code,
|
||||
EnumValue=x.Code,
|
||||
Value=x.Value,
|
||||
ValueCN=x.ValueCN
|
||||
DictionaryCode = x.Parent.Code,
|
||||
EnumValue = x.Code,
|
||||
Value = x.Value,
|
||||
ValueCN = x.ValueCN
|
||||
}).ToListAsync();
|
||||
|
||||
|
||||
|
|
@ -88,7 +134,7 @@ public static class CommonEmailHelper
|
|||
inDto.DictionaryList.ForEach(x =>
|
||||
{
|
||||
|
||||
var dic = dicList.Where(y => y.EnumValue == x.EnumValue && y.DictionaryCode == x.DictionaryCode).FirstOrDefault();
|
||||
var dic = dicList.Where(y => y.EnumValue == x.EnumValue && y.DictionaryCode.Equals(x.DictionaryCode,StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
|
||||
result.Add(dic == null ? string.Empty : (inDto.IsEn_US ? dic.Value : dic.ValueCN));
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,44 +1,49 @@
|
|||
using AutoMapper;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Contracts.DTO;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.MassTransit.Command;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using MassTransit;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace IRaCIS.Core.Application.MassTransit.Consumer
|
||||
{
|
||||
public class ConsistencyCheckConsumer : IConsumer<ConsistenCheckCommand>
|
||||
public class ConsistencyCheckConsumer(
|
||||
IRepository<DicomStudy> _studyRepository,
|
||||
IUserInfo _userInfo,
|
||||
IRepository<Subject> _subjectRepository,
|
||||
IRepository<NoneDicomStudy> _noneDicomStudyRepository,
|
||||
IRepository<SubjectVisit> _subjectVisitRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IOSSService _oSSService,
|
||||
IMapper _mapper,
|
||||
IDictionaryService _dictionaryService,
|
||||
IRepository<CommonDocument> _commonDocumentRepository,
|
||||
IStringLocalizer _localizer,
|
||||
IWebHostEnvironment _hostEnvironment,
|
||||
IRepository<InspectionFile> _inspectionFileRepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> _systemEmailSendConfig
|
||||
) : IConsumer<ConsistenCheckCommand>, IConsumer<ConsistenFullCheckCommand>
|
||||
{
|
||||
|
||||
private readonly IRepository<DicomStudy> _studyRepository;
|
||||
private readonly IUserInfo _userInfo;
|
||||
private readonly IRepository<Subject> _subjectRepository;
|
||||
private readonly IRepository<SubjectVisit> _subjectVisitRepository;
|
||||
private readonly IRepository<TrialSite> _trialSiteRepository;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IRepository<NoneDicomStudy> _noneDicomStudyRepository;
|
||||
public IStringLocalizer _localizer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数注入
|
||||
/// </summary>
|
||||
|
||||
public ConsistencyCheckConsumer(IRepository<DicomStudy> studyRepository, IUserInfo userInfo,
|
||||
IRepository<Subject> subjectRepository, IRepository<SubjectVisit> subjectVisitRepository,
|
||||
IRepository<TrialSite> trialSiteRepository, IRepository<NoneDicomStudy> noneDicomStudyRepository,
|
||||
IMapper mapper, IStringLocalizer localizer)
|
||||
{
|
||||
_noneDicomStudyRepository = noneDicomStudyRepository;
|
||||
_studyRepository = studyRepository;
|
||||
_userInfo = userInfo;
|
||||
_subjectRepository = subjectRepository;
|
||||
_subjectVisitRepository = subjectVisitRepository;
|
||||
_trialSiteRepository = trialSiteRepository;
|
||||
_mapper = mapper;
|
||||
_localizer = localizer;
|
||||
}
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = _systemEmailSendConfig.CurrentValue;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -47,6 +52,9 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
|
|||
|
||||
var trialId = context.Message.TrialId;
|
||||
|
||||
//系统定义MRI 到底是MR 还是MRI
|
||||
var mriModality = await _dictionaryRepository.Where(t => t.Parent.Code == "Modality").Where(t => t.Code == "MRI").Select(t => t.Value).FirstNotNullAsync();
|
||||
|
||||
//处理Excel大小写
|
||||
context.Message.ETCList.ForEach(t => { t.Modality = t.Modality.ToUpper().Trim(); t.StudyDate = Convert.ToDateTime(t.StudyDate).ToString("yyyy-MM-dd"); t.SiteCode = t.SiteCode.ToUpper().Trim(); t.VisitName = t.VisitName.ToUpper().Trim(); t.SubjectCode = t.SubjectCode.ToUpper().Trim(); });
|
||||
var etcList = context.Message.ETCList;
|
||||
|
|
@ -55,8 +63,8 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
|
|||
|
||||
//subjectVisitLambda2= subjectVisitLambda2.And(x => x.CheckState == CheckStateEnum.ToCheck && x.AuditState == AuditStateEnum.QCPassed || (x.CheckState == CheckStateEnum.CVIng && x.AuditState == AuditStateEnum.QCPassed));
|
||||
|
||||
Expression<Func<SubjectVisit, bool>> subjectVisitLambda = x => x.TrialId == trialId &&
|
||||
(x.CheckState == CheckStateEnum.ToCheck && x.AuditState == AuditStateEnum.QCPassed || (x.CheckState == CheckStateEnum.CVIng && x.AuditState == AuditStateEnum.QCPassed));
|
||||
Expression<Func<SubjectVisit, bool>> subjectVisitLambda = x => x.TrialId == trialId && x.Subject.IsSubjectQuit == false && x.AuditState == AuditStateEnum.QCPassed &&
|
||||
(x.CheckState == CheckStateEnum.ToCheck || x.CheckState == CheckStateEnum.CVIng);
|
||||
|
||||
var dicomQuery = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
|
||||
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
|
||||
|
|
@ -105,7 +113,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
|
|||
foreach (var sv in svExcelGroup)
|
||||
{
|
||||
|
||||
//Excel 的数据 在IRC 中可以找到该访视
|
||||
//Excel 的数据 在IRC 中可以找到该访视(一致性核查中,或者待核查)
|
||||
if (dbCheckList.Any(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName))
|
||||
{
|
||||
|
||||
|
|
@ -115,6 +123,16 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
|
|||
var etcVisitStudyList = etcList.Where(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName).ToList();
|
||||
|
||||
|
||||
//以我们系统数据库为准 ,判断是MR 还是MRI, 把Excel 里的MR 或者MRI 处理成一致,MR MRI 是一致的
|
||||
foreach (var item in etcVisitStudyList)
|
||||
{
|
||||
if (item.Modality == "MR" || item.Modality == "MRI")
|
||||
{
|
||||
item.Modality = mriModality;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
StringBuilder dialogMsg = new StringBuilder();
|
||||
//---您好,根据本系统自动识别,该受试者当前访视在IRC系统中已提交的影像检查情况如下:
|
||||
|
|
@ -179,14 +197,14 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
|
|||
var dbExceptExcel = dbVisitStudyList.Except(etcVisitStudyList);
|
||||
|
||||
// excel 存在
|
||||
var excelExceptDB = etcVisitStudyList.Except(dbCheckList);
|
||||
var excelExceptDB = etcVisitStudyList.Except(dbVisitStudyList);
|
||||
|
||||
//ETC 和系统的完全一致 两者没有差别
|
||||
if (dbExceptExcel.Count() == 0 && excelExceptDB.Count() == 0)
|
||||
{
|
||||
dialogMsg.AppendLine($"<br/>");
|
||||
//---核对EDC数据,完全一致, 审核通过
|
||||
dialogMsg.AppendLine(_localizer["ConsistencyVerification_EDCA"]);
|
||||
dialogMsg.AppendLine(_localizer["ConsistencyVerification_EDCA", _systemEmailConfig.SystemShortName]);
|
||||
|
||||
|
||||
// dialogMsg.AppendLine(@$"<br>说明:为高效解决/处理以上全部质疑问题,麻烦您准确核实实际影像检查情况。请注意影像日期与实际检查的日期可能会不一致,部分检查(如PET -CT)可能同时存在多种模态影像。准确核实后,请回复该访视正确的影像检查情况。");
|
||||
|
|
@ -196,7 +214,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
|
|||
dbSV.CheckChallengeState = CheckChanllengeTypeEnum.Closed;
|
||||
|
||||
//---核对EDC数据,完全一致
|
||||
dbSV.CheckResult = _localizer["ConsistencyVerification_EDCB"];
|
||||
dbSV.CheckResult = _localizer["ConsistencyVerification_EDCB", _systemEmailConfig.SystemShortName];
|
||||
//---自动核查通过
|
||||
dbSV.ManualPassReason = _localizer["ConsistencyVerification_Auto"];
|
||||
|
||||
|
|
@ -224,7 +242,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
|
|||
{
|
||||
num++;
|
||||
//影像检查(EDC 缺少) ConsistencyVerification_EdcL
|
||||
dialogMsg.AppendLine($"<br/><div style='text-indent: 20px;'>{num}. {_localizer["ConsistencyVerification_EdcL", item.StudyDate, item.Modality]}</div>");
|
||||
dialogMsg.AppendLine($"<br/><div style='text-indent: 20px;'>{num}. {_localizer["ConsistencyVerification_EdcL", item.StudyDate, item.Modality/*, _systemEmailConfig.SystemShortName*/]}</div>");
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -232,15 +250,15 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
|
|||
{
|
||||
num++;
|
||||
// 影像检查(IRC 缺少)
|
||||
dialogMsg.AppendLine($"<br/><div style='text-indent: 20px;'>{num}. {_localizer["ConsistencyVerification_IrcLi", item.StudyDate, item.Modality]}</div>");
|
||||
dialogMsg.AppendLine($"<br/><div style='text-indent: 20px;'>{num}. {_localizer["ConsistencyVerification_IrcLi", item.StudyDate, item.Modality, _systemEmailConfig.SystemShortName]}</div>");
|
||||
}
|
||||
|
||||
dialogMsg.AppendLine($"<br/>");
|
||||
dialogMsg.AppendLine(@$"<div>{_localizer["ConsistencyVerification_Desc"]}</div>");
|
||||
|
||||
dbSV.CheckResult = _localizer["ConsistencyVerification_Conf"] +
|
||||
String.Join(" | ", dbExceptExcel.Select(t => $"{_localizer["ConsistencyVerification_EdcL", t.StudyDate, t.Modality]}")) + " | "
|
||||
+ String.Join(" | ", excelExceptDB.Select(t => $"{_localizer["ConsistencyVerification_IrcLi", t.StudyDate, t.Modality]}"));
|
||||
dbSV.CheckResult =
|
||||
String.Join(" | ", dbExceptExcel.Select(t => $"{_localizer["ConsistencyVerification_EdcL", t.StudyDate, t.Modality/*, _systemEmailConfig.SystemShortName*/]}")) + " | "
|
||||
+ String.Join(" | ", excelExceptDB.Select(t => $"{_localizer["ConsistencyVerification_IrcLi", t.StudyDate, t.Modality, _systemEmailConfig.SystemShortName]}"));
|
||||
|
||||
//新增一致性核查质疑记录
|
||||
|
||||
|
|
@ -279,12 +297,169 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
|
|||
}
|
||||
await _subjectVisitRepository.SaveChangesAsync();
|
||||
|
||||
//await context.RespondAsync<ConsistenCheckResult>(new
|
||||
//{
|
||||
|
||||
//});
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<ConsistenFullCheckCommand> context)
|
||||
{
|
||||
var inspectionFileId = context.Message.InspectionFileId;
|
||||
var trialId = context.Message.TrialId;
|
||||
|
||||
//系统定义MRI 到底是MR 还是MRI
|
||||
var mriModality = await _dictionaryRepository.Where(t => t.Parent.Code == "Modality").Where(t => t.Code == "MRI").Select(t => t.Value).FirstNotNullAsync();
|
||||
|
||||
//处理Excel大小写
|
||||
context.Message.ETCList.ForEach(t => { t.Modality = t.Modality.ToUpper().Trim(); t.StudyDate = Convert.ToDateTime(t.StudyDate).ToString("yyyy-MM-dd"); t.SiteCode = t.SiteCode.ToUpper().Trim(); t.VisitName = t.VisitName.ToUpper().Trim(); t.SubjectCode = t.SubjectCode.ToUpper().Trim(); });
|
||||
var etcList = context.Message.ETCList;
|
||||
|
||||
|
||||
Expression<Func<SubjectVisit, bool>> subjectVisitLambda = x => x.TrialId == trialId /*&& x.Subject.IsSubjectQuit == false*/ && x.AuditState == AuditStateEnum.QCPassed;
|
||||
|
||||
var dicomQuery = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
|
||||
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
|
||||
join study in _studyRepository.AsQueryable() on sv.Id equals study.SubjectVisitId
|
||||
select new CheckDBModel()
|
||||
{
|
||||
SubjectStatus = sv.Subject.Status,
|
||||
SubjectVisitId = sv.Id,
|
||||
SiteCode = sv.TrialSite.TrialSiteCode,
|
||||
StudyDate = study.StudyTime == null ? string.Empty : ((DateTime)study.StudyTime).ToString("yyyy-MM-dd"),
|
||||
StudyId = study.Id,
|
||||
Modality = study.ModalityForEdit,
|
||||
SubjectCode = subject.Code,
|
||||
VisitName = sv.VisitName,
|
||||
};
|
||||
|
||||
var noneDicomQuey = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
|
||||
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
|
||||
join noneDicomStudy in _noneDicomStudyRepository.AsQueryable() on sv.Id equals noneDicomStudy.SubjectVisitId
|
||||
select new CheckDBModel()
|
||||
{
|
||||
SubjectStatus = sv.Subject.Status,
|
||||
SubjectVisitId = sv.Id,
|
||||
SiteCode = sv.TrialSite.TrialSiteCode,
|
||||
StudyDate = noneDicomStudy.ImageDate.ToString("yyyy-MM-dd"),
|
||||
StudyId = noneDicomStudy.Id,
|
||||
Modality = noneDicomStudy.Modality,
|
||||
SubjectCode = subject.Code,
|
||||
VisitName = sv.VisitName,
|
||||
};
|
||||
|
||||
var dbList = (await dicomQuery.ToListAsync()).Union(await noneDicomQuey.ToListAsync()).ToList();
|
||||
|
||||
//处理数据库 大小写
|
||||
dbList.ForEach(t => { t.Modality = t.Modality.ToUpper().Trim(); t.SiteCode = t.SiteCode.ToUpper().Trim(); t.VisitName = t.VisitName.ToUpper().Trim(); t.SubjectCode = t.SubjectCode.ToUpper().Trim(); });
|
||||
|
||||
var dbCheckList = _mapper.Map<List<CheckViewModel>>(dbList);
|
||||
|
||||
|
||||
//按照Excel数据访视分组 按照数据库的数据 一个个的访视对比
|
||||
var svExcelGroup = etcList.GroupBy(t => new { t.SiteCode, t.SubjectCode, t.VisitName })
|
||||
.Select(g => new { g.Key.SubjectCode, g.Key.VisitName, g.Key.SiteCode, ExcelStudyList = g.ToList() }).ToList();
|
||||
|
||||
var fullCheckResultList = new List<FullCheckResult>();
|
||||
|
||||
|
||||
foreach (var sv in svExcelGroup)
|
||||
{
|
||||
|
||||
//Excel 的数据 在IRC 中可以找到该访视(一致性核查中,或者待核查)
|
||||
if (dbCheckList.Any(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName))
|
||||
{
|
||||
|
||||
var dbVisitStudyList = dbList.Where(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName).ToList();
|
||||
|
||||
//找到etc 当前visit site 和subject 一致的检查列表
|
||||
var etcVisitStudyList = etcList.Where(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName).ToList();
|
||||
|
||||
|
||||
//以我们系统数据库为准 ,判断是MR 还是MRI, 把Excel 里的MR 或者MRI 处理成一致,MR MRI 是一致的
|
||||
foreach (var item in etcVisitStudyList)
|
||||
{
|
||||
if (item.Modality == "MR" || item.Modality == "MRI")
|
||||
{
|
||||
item.Modality = mriModality;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//etc 和数据库 并集
|
||||
var unionList = dbVisitStudyList.Union(etcVisitStudyList);
|
||||
// 数据库存在
|
||||
var dbExceptExcel = dbVisitStudyList.Except(etcVisitStudyList);
|
||||
|
||||
// excel 存在
|
||||
var excelExceptDB = etcVisitStudyList.Except(dbVisitStudyList);
|
||||
|
||||
var dbCurrentVisitFirst = dbVisitStudyList.First();
|
||||
|
||||
//ETC 和系统的完全一致 两者没有差别
|
||||
if (dbExceptExcel.Count() == 0 && excelExceptDB.Count() == 0)
|
||||
{
|
||||
|
||||
|
||||
//每个受试者每个访视加一条记录
|
||||
fullCheckResultList.Add(new FullCheckResult()
|
||||
{
|
||||
SubjectStatus = dbCurrentVisitFirst.SubjectStatus,
|
||||
CheckTime = DateTime.Now,
|
||||
CheckState=CheckStateEnum.CVPassed,
|
||||
SiteCode = dbCurrentVisitFirst.SiteCode,
|
||||
SubjectCode = dbCurrentVisitFirst.SubjectCode,
|
||||
VisitName = dbCurrentVisitFirst.VisitName,
|
||||
Modalitys = string.Join('、', dbVisitStudyList.Select(t => t.Modality)),
|
||||
LatestScanDateStr = dbVisitStudyList.Select(t => t.StudyDate).MaxBy(d => DateTime.Parse(d)) ?? ""
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var checkResult =
|
||||
String.Join(" | ", dbExceptExcel.Select(t => $"{_localizer["ConsistencyVerification_EdcL", t.StudyDate, t.Modality/*, _systemEmailConfig.SystemShortName*/]}")) + " | "
|
||||
+ String.Join(" | ", excelExceptDB.Select(t => $"{_localizer["ConsistencyVerification_IrcLi", t.StudyDate, t.Modality, _systemEmailConfig.SystemShortName]}"));
|
||||
|
||||
//每个受试者每个访视加一条记录
|
||||
fullCheckResultList.Add(new FullCheckResult()
|
||||
{
|
||||
SubjectStatus = dbCurrentVisitFirst.SubjectStatus,
|
||||
CheckState = CheckStateEnum.None,
|
||||
CheckTime = DateTime.Now,
|
||||
SiteCode = dbCurrentVisitFirst.SiteCode,
|
||||
SubjectCode = dbCurrentVisitFirst.SubjectCode,
|
||||
VisitName = dbCurrentVisitFirst.VisitName,
|
||||
Modalitys = string.Join('、', dbVisitStudyList.Select(t => t.Modality)),
|
||||
LatestScanDateStr = dbVisitStudyList.Select(t => t.StudyDate).MaxBy(d => DateTime.Parse(d)) ?? "",
|
||||
CheckResult = checkResult
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//导到Excel 上传oss 回更记录状态
|
||||
|
||||
var list = fullCheckResultList;
|
||||
|
||||
var exportInfo = new ExcelExportInfo();
|
||||
|
||||
exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId);
|
||||
exportInfo.CurrentTime = ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId);
|
||||
|
||||
var fileStreamResult = (FileStreamResult)await ExcelExportHelper.DataExportAsync(StaticData.Export.TrialConsistentFUllCheckList_Export, exportInfo, exportInfo.TrialCode, _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(FullCheckResult));
|
||||
|
||||
var ossRelativePath = await _oSSService.UploadToOSSAsync(fileStreamResult.FileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", "DataReconciliation");
|
||||
|
||||
|
||||
//var add = await _inspectionFileRepository.FindAsync(inspectionFileId);
|
||||
//add.CheckState = EDCCheckState.Success;
|
||||
//add.ResultPath = ossRelativePath;
|
||||
|
||||
//更新
|
||||
await _inspectionFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == inspectionFileId, u => new InspectionFile() { CheckState = EDCCheckState.Success, ResultPath = ossRelativePath });
|
||||
|
||||
await _subjectVisitRepository.SaveChangesAsync();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.MassTransit.Consumer.Dto
|
||||
{
|
||||
public class SendImageReuploadEmailInDto
|
||||
{
|
||||
public EmailNoticeConfig EmailNoticeConfig { get; set; }
|
||||
|
||||
public EmailBusinessScenario Scenario { get; set; }
|
||||
|
||||
public SubjectVisit SubjectVisit { get; set; }
|
||||
|
||||
public Guid TrialId { get; set; }
|
||||
public List<UserTypeEnum> UserTypes { get; set; } = null;
|
||||
|
||||
public ImageBackStateEnum ImageBackStateEnum { get; set; } = ImageBackStateEnum.None;
|
||||
|
||||
public RequestBackStateEnum RequestBackStateEnum { get; set; } = RequestBackStateEnum.NotRequest;
|
||||
|
||||
public ReReadingApplyState ReReadingApplyState { get; set; } = ReReadingApplyState.Default;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,510 @@
|
|||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.MassTransit.Command;
|
||||
using IRaCIS.Core.Application.MassTransit.Consumer.Dto;
|
||||
using IRaCIS.Core.Application.Service.Reading.Dto;
|
||||
using IRaCIS.Core.Domain;
|
||||
using IRaCIS.Core.Domain._DomainEvent;
|
||||
using IRaCIS.Core.Domain.BaseModel;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore.Common;
|
||||
using MassTransit;
|
||||
using MassTransit.Mediator;
|
||||
using MassTransit.Scheduling;
|
||||
using Medallion.Threading;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using Newtonsoft.Json;
|
||||
using Panda.DynamicWebApi.Attributes;
|
||||
using RestSharp;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
|
||||
/// <summary>
|
||||
/// 影像重传和阅片人筛选相关邮件发送
|
||||
/// </summary>
|
||||
public class ImageConsumer(
|
||||
IRepository<UserRole> _userRoleRepository,
|
||||
IRepository<TrialUserRole> _trialUseRoleRepository,
|
||||
IRepository<SubjectVisit> _subjectVisitRepository,
|
||||
IRepository<Trial> _trialRepository,
|
||||
IRepository<Enroll> _enrollRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<ReadModule> _readModuleRepository,
|
||||
IRepository<EmailNoticeUserType> _emailNoticeUserTypeRepository,
|
||||
IRepository<TrialSite> _trialSiteRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) :
|
||||
IConsumer<DirectApplyReupdloadEvent>,
|
||||
IConsumer<CheckReuploadEvent>,
|
||||
IConsumer<QCChanllengeReuploadEvent>,
|
||||
IConsumer<HaveReadVisitTaskReReading>,
|
||||
IConsumer<ReviewerPMApplyEvent>,
|
||||
IConsumer<ReviewerSPMApprovedEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
||||
/// <summary>
|
||||
/// 处理直接申请流程的重传事件
|
||||
/// </summary>
|
||||
public async Task Consume(ConsumeContext<DirectApplyReupdloadEvent> context)
|
||||
{
|
||||
Console.WriteLine("发送【影像重传-直接申请流程】邮件!!!");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
var subjectVisitId = context.Message.SubjectVisitId;
|
||||
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.Subject).FirstNotNullAsync();
|
||||
var trialId = subjectVisit.TrialId;
|
||||
List<UserTypeEnum> userTypes = null;
|
||||
// 根据申请角色和状态确定邮件场景
|
||||
EmailBusinessScenario scenario;
|
||||
if (context.Message.ImageBackState == ImageBackStateEnum.None)
|
||||
{
|
||||
switch (context.Message.ApplyUserRole)
|
||||
{
|
||||
case ImageBackApplyEnum.IQCRequestBack:
|
||||
scenario = EmailBusinessScenario.ImageQC_IQCApplyRe_Uploading; // 64
|
||||
break;
|
||||
case ImageBackApplyEnum.CRCRequestBack:
|
||||
scenario = EmailBusinessScenario.ImageUploadCRCApplyRe_Uploading; // 65
|
||||
break;
|
||||
default:
|
||||
scenario = EmailBusinessScenario.ImageQC_IQCApplyRe_Uploading; // 64
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
scenario = EmailBusinessScenario.ImageReUpload_PMApprovalRe_Uploading; // 66
|
||||
if (context.Message.ApplyUserRole == ImageBackApplyEnum.IQCRequestBack)
|
||||
{
|
||||
userTypes = new List<UserTypeEnum>() { UserTypeEnum.IQC, UserTypeEnum.ClinicalResearchCoordinator };
|
||||
}
|
||||
else if (context.Message.ApplyUserRole == ImageBackApplyEnum.CRCRequestBack)
|
||||
{
|
||||
userTypes = new List<UserTypeEnum>() { UserTypeEnum.ClinicalResearchCoordinator };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsEnable && t.IsAutoSend).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
var inDto = new SendImageReuploadEmailInDto
|
||||
{
|
||||
EmailNoticeConfig = emailConfig,
|
||||
Scenario = scenario,
|
||||
SubjectVisit = subjectVisit,
|
||||
TrialId = trialId,
|
||||
UserTypes = userTypes,
|
||||
ImageBackStateEnum = context.Message.ImageBackState
|
||||
};
|
||||
await SendImageReuploadEmail(inDto);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理一致性核查申请重传事件
|
||||
/// </summary>
|
||||
public async Task Consume(ConsumeContext<CheckReuploadEvent> context)
|
||||
{
|
||||
Console.WriteLine("发送【一致性核查-影像重传】邮件!!!");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
var subjectVisitId = context.Message.SubjectVisitId;
|
||||
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.Subject).FirstNotNullAsync();
|
||||
var trialId = subjectVisit.TrialId;
|
||||
|
||||
// 根据状态确定邮件场景
|
||||
EmailBusinessScenario scenario;
|
||||
switch (context.Message.RequestBackState)
|
||||
{
|
||||
case RequestBackStateEnum.CRC_RequestBack:
|
||||
scenario = EmailBusinessScenario.DataReconciliation_CRCApplyRe_Uploading; // 62
|
||||
break;
|
||||
case RequestBackStateEnum.PM_AgressBack:
|
||||
case RequestBackStateEnum.PM_NotAgree:
|
||||
scenario = EmailBusinessScenario.DataReconciliation_PMApprovalRe_Uploading; // 63
|
||||
break;
|
||||
default:
|
||||
return; // 其他状态不发送邮件
|
||||
}
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsEnable && t.IsAutoSend).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
var inDto = new SendImageReuploadEmailInDto
|
||||
{
|
||||
EmailNoticeConfig = emailConfig,
|
||||
Scenario = scenario,
|
||||
SubjectVisit = subjectVisit,
|
||||
TrialId = trialId,
|
||||
RequestBackStateEnum = context.Message.RequestBackState,
|
||||
};
|
||||
await SendImageReuploadEmail(inDto);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理质疑CRC申请重传事件
|
||||
/// </summary>
|
||||
public async Task Consume(ConsumeContext<QCChanllengeReuploadEvent> context)
|
||||
{
|
||||
Console.WriteLine("发送【影像质疑-影像重传】邮件!!!");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
var subjectVisitId = context.Message.SubjectVisitId;
|
||||
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.Subject).FirstNotNullAsync();
|
||||
var trialId = subjectVisit.TrialId;
|
||||
|
||||
// 根据状态确定邮件场景
|
||||
EmailBusinessScenario scenario;
|
||||
switch (context.Message.ReuploadEnum)
|
||||
{
|
||||
case QCChanllengeReuploadEnum.CRCRequestReupload:
|
||||
scenario = EmailBusinessScenario.ImageQueryCRCApplyRe_Uploading; // 60
|
||||
break;
|
||||
case QCChanllengeReuploadEnum.QCAgreeUpload:
|
||||
scenario = EmailBusinessScenario.ImageQuery_IQCApprovalRe_Uploading; // 61
|
||||
break;
|
||||
default:
|
||||
return; // 其他状态不发送邮件
|
||||
}
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsEnable && t.IsAutoSend).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
var inDto = new SendImageReuploadEmailInDto
|
||||
{
|
||||
EmailNoticeConfig = emailConfig,
|
||||
Scenario = scenario,
|
||||
SubjectVisit = subjectVisit,
|
||||
TrialId = trialId
|
||||
};
|
||||
await SendImageReuploadEmail(inDto);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理已阅任务重阅申请事件
|
||||
/// </summary>
|
||||
public async Task Consume(ConsumeContext<HaveReadVisitTaskReReading> context)
|
||||
{
|
||||
Console.WriteLine("发送【重阅-影像重传】邮件!!!");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
var visitTaskId = context.Message.VisitTaskId;
|
||||
// 获取VisitTask关联的SubjectVisit
|
||||
var visitTask = await _visitTaskRepository.Where(x => x.Id == visitTaskId).Include(x => x.Subject).Include(x => x.SourceSubjectVisit).FirstNotNullAsync();
|
||||
var subjectVisit = new SubjectVisit() { };
|
||||
if (visitTask.SourceSubjectVisitId != null)
|
||||
{
|
||||
subjectVisit = visitTask.SourceSubjectVisit;
|
||||
}
|
||||
else if (visitTask.SouceReadModuleId != null)
|
||||
{
|
||||
var readModule = await _readModuleRepository.Where(x => x.Id == visitTask.SouceReadModuleId).Include(x => x.SubjectVisit).FirstOrDefaultAsync();
|
||||
subjectVisit = readModule.SubjectVisit;
|
||||
}
|
||||
|
||||
var trialId = subjectVisit.TrialId;
|
||||
|
||||
// 根据状态确定邮件场景
|
||||
EmailBusinessScenario scenario;
|
||||
switch (context.Message.ReReadingApplyState)
|
||||
{
|
||||
case ReReadingApplyState.TrialGroupHaveApplyed:
|
||||
scenario = EmailBusinessScenario.ReadTracking_PMApplyRe_Reading; // 67
|
||||
break;
|
||||
case ReReadingApplyState.Agree:
|
||||
case ReReadingApplyState.Reject:
|
||||
scenario = EmailBusinessScenario.Re_ReadApproval_SPMCPMApprovalRe_Reading; // 68
|
||||
break;
|
||||
default:
|
||||
return; // 其他状态不发送邮件
|
||||
}
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsEnable && t.IsAutoSend).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
var inDto = new SendImageReuploadEmailInDto
|
||||
{
|
||||
EmailNoticeConfig = emailConfig,
|
||||
Scenario = scenario,
|
||||
SubjectVisit = subjectVisit,
|
||||
TrialId = trialId,
|
||||
ReReadingApplyState= context.Message.ReReadingApplyState,
|
||||
};
|
||||
await SendImageReuploadEmail(inDto);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送影像重传相关邮件的通用方法
|
||||
/// </summary>
|
||||
private async Task SendImageReuploadEmail(SendImageReuploadEmailInDto inDto)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
inDto.SubjectVisit = await _subjectVisitRepository.Where(x => x.Id == inDto.SubjectVisit.Id).Include(x => x.Subject).FirstNotNullAsync();
|
||||
// 获取项目信息
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == inDto.TrialId);
|
||||
|
||||
List<UserTypeEnum> filterUserTypeList = new List<UserTypeEnum>()
|
||||
{
|
||||
UserTypeEnum.ClinicalResearchCoordinator,
|
||||
UserTypeEnum.CRA,
|
||||
};
|
||||
|
||||
// 根据不同场景获取不同角色的用户 先排除CRC和CRA
|
||||
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == inDto.TrialId && !x.TrialUser.IsDeleted && !filterUserTypeList.Contains(x.UserRole.UserTypeEnum) ).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
|
||||
// CRC和CRA单独取
|
||||
var crcAndcraUserList = await _trialSiteRepository.Where(x => x.Id == inDto.SubjectVisit.TrialSiteId).SelectMany(x => x.CRCUserList.Select(y => y.UserRole)).ToListAsync();
|
||||
trialUserList.AddRange(crcAndcraUserList);
|
||||
|
||||
// 根据场景确定收件人
|
||||
List<UserRole> toUserList = new List<UserRole>();
|
||||
List<UserRole> ccUserList = new List<UserRole>();
|
||||
|
||||
var emailNoticeUserList = await _emailNoticeUserTypeRepository.Where(x => x.EmailNoticeConfigId == inDto.EmailNoticeConfig.Id).ToListAsync();
|
||||
var userTypeEnumList = emailNoticeUserList.Select(x => x.UserType).ToList();
|
||||
if (inDto.UserTypes != null)
|
||||
{
|
||||
userTypeEnumList = inDto.UserTypes;
|
||||
}
|
||||
|
||||
if (inDto.UserTypes == null)
|
||||
{
|
||||
var toList = emailNoticeUserList.Where(x => x.EmailUserType == EmailUserType.To).Select(x => x.UserType).ToList();
|
||||
toUserList = trialUserList.Where(x => toList.Contains(x.UserTypeEnum)).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
var toList = inDto.UserTypes;
|
||||
toUserList = trialUserList.Where(x => toList.Contains(x.UserTypeEnum)).ToList();
|
||||
}
|
||||
|
||||
var ccList = emailNoticeUserList.Where(x => x.EmailUserType == EmailUserType.Copy).Select(x => x.UserType).ToList();
|
||||
ccUserList = trialUserList.Where(x => ccList.Contains(x.UserTypeEnum)).ToList();
|
||||
|
||||
// 如果没有收件人,则不发送邮件
|
||||
if (toUserList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
List<DictionaryDto> dictionaryDtos = new List<DictionaryDto>() {
|
||||
new DictionaryDto (){DictionaryCode= "YesOrNo",EnumValue=inDto.SubjectVisit.IsUrgent.ToString().ToLower(), }, //是否加急
|
||||
};
|
||||
switch (inDto.Scenario)
|
||||
{
|
||||
case EmailBusinessScenario.Re_ReadApproval_SPMCPMApprovalRe_Reading:
|
||||
dictionaryDtos.Add(new DictionaryDto() { DictionaryCode = "ReReadingApplyState", EnumValue = inDto.ReReadingApplyState.GetEnumInt(), });
|
||||
break;
|
||||
case EmailBusinessScenario.DataReconciliation_PMApprovalRe_Uploading:
|
||||
dictionaryDtos.Add(new DictionaryDto() { DictionaryCode = "RequestBackState", EnumValue = inDto.RequestBackStateEnum.GetEnumInt(), });
|
||||
break;
|
||||
default:
|
||||
dictionaryDtos.Add(new DictionaryDto() { DictionaryCode = "ImageBackStateEnum", EnumValue = inDto.ImageBackStateEnum.GetEnumInt(), });//审批结果
|
||||
break;
|
||||
}
|
||||
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
|
||||
{
|
||||
|
||||
DictionaryRepository = _dictionaryRepository,
|
||||
IsEn_US = isEn_US,
|
||||
DictionaryList = dictionaryDtos
|
||||
});
|
||||
|
||||
foreach (var userinfo in toUserList)
|
||||
{
|
||||
var messageToSend = new MimeMessage();
|
||||
// 发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
// 添加抄送
|
||||
foreach (var ccUser in ccUserList)
|
||||
{
|
||||
messageToSend.Cc.Add(new MailboxAddress(String.Empty, ccUser.EMail));
|
||||
}
|
||||
// 格式化邮件内容
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
|
||||
{
|
||||
var subjectCode = inDto.SubjectVisit.Subject.Code;
|
||||
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, inDto.SubjectVisit.VisitName);
|
||||
var htmlBodyStr = string.Format(
|
||||
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
|
||||
userinfo.FullName, // 用户名 {0}
|
||||
trialInfo.ExperimentName, // 项目 {1}
|
||||
subjectCode, // 受试者 {2}
|
||||
inDto.SubjectVisit.VisitName, // 访视 {3}
|
||||
dictionValue[0], // 是否加急 {4}
|
||||
dictionValue[1], // 审批结果 {5}
|
||||
_systemEmailConfig.SiteUrl // 链接 {6}
|
||||
);
|
||||
|
||||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(inDto.EmailNoticeConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理PM申请阅片人筛选事件
|
||||
/// </summary>
|
||||
public async Task Consume(ConsumeContext<ReviewerPMApplyEvent> context)
|
||||
{
|
||||
Console.WriteLine("发送【阅片人筛选-PM申请审批】邮件!!!");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
// 获取项目信息
|
||||
var enrollIdList = context.Message.EnrollIdList;
|
||||
if (enrollIdList == null || !enrollIdList.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取第一个Enroll的TrialId
|
||||
var enrollId = enrollIdList.First();
|
||||
var enroll = await _enrollRepository.Where(x => x.Id == enrollId).FirstNotNullAsync();
|
||||
var trialId = enroll.TrialId;
|
||||
|
||||
// 设置邮件场景
|
||||
var scenario = EmailBusinessScenario.ReviewerSelection_PMApplyApproving; // 69
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsEnable && t.IsAutoSend).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
await SendReviewerSelectionEmail(emailConfig, trialId, enrollIdList);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理SPM审批阅片人筛选事件
|
||||
/// </summary>
|
||||
public async Task Consume(ConsumeContext<ReviewerSPMApprovedEvent> context)
|
||||
{
|
||||
Console.WriteLine("发送【阅片人筛选-SPM审批】邮件!!!");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
// 获取项目信息
|
||||
var enrollIdList = context.Message.EnrollIdList;
|
||||
if (enrollIdList == null || !enrollIdList.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取第一个Enroll的TrialId
|
||||
var enrollId = enrollIdList.First();
|
||||
var enroll = await _enrollRepository.Where(x => x.Id == enrollId).FirstNotNullAsync();
|
||||
var trialId = enroll.TrialId;
|
||||
|
||||
// 设置邮件场景
|
||||
var scenario = EmailBusinessScenario.ReviewerSelection_SPMCPMApproval; // 70
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsEnable && t.IsAutoSend).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
await SendReviewerSelectionEmail(emailConfig, trialId, enrollIdList);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送阅片人筛选相关邮件的通用方法
|
||||
/// </summary>
|
||||
private async Task SendReviewerSelectionEmail(EmailNoticeConfig emailNoticeConfig, Guid trialId, List<Guid> enrollIdList)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
// 获取项目信息
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
|
||||
|
||||
// 根据不同场景获取不同角色的用户
|
||||
var trialUser = await _trialUseRoleRepository.Where(x => x.TrialId == trialId && !x.TrialUser.IsDeleted).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
|
||||
// 根据场景确定收件人
|
||||
List<UserRole> toUserList = new List<UserRole>();
|
||||
List<UserRole> ccUserList = new List<UserRole>();
|
||||
|
||||
var emailNoticeUserList = await _emailNoticeUserTypeRepository.Where(x => x.EmailNoticeConfigId == emailNoticeConfig.Id).ToListAsync();
|
||||
|
||||
var toList = emailNoticeUserList.Where(x => x.EmailUserType == EmailUserType.To).Select(x => x.UserType).ToList();
|
||||
|
||||
toUserList = trialUser.Where(x => toList.Contains(x.UserTypeEnum)).ToList();
|
||||
|
||||
var ccList = emailNoticeUserList.Where(x => x.EmailUserType == EmailUserType.Copy && x.UserType != UserTypeEnum.ClinicalResearchCoordinator).Select(x => x.UserType).ToList();
|
||||
|
||||
ccUserList = trialUser.Where(x => ccList.Contains(x.UserTypeEnum)).ToList();
|
||||
|
||||
|
||||
// 如果没有收件人,则不发送邮件
|
||||
if (toUserList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var userinfo in toUserList)
|
||||
{
|
||||
var messageToSend = new MimeMessage();
|
||||
// 发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
// 添加抄送
|
||||
foreach (var ccUser in ccUserList)
|
||||
{
|
||||
messageToSend.Cc.Add(new MailboxAddress(String.Empty, ccUser.EMail));
|
||||
}
|
||||
|
||||
// 格式化邮件内容
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
|
||||
{
|
||||
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo);
|
||||
var htmlBodyStr = string.Format(
|
||||
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
|
||||
userinfo.FullName, // 用户名 {0}
|
||||
trialInfo.ExperimentName, // 项目 {1}
|
||||
"", // 受试者 {2} - 阅片人筛选不涉及受试者
|
||||
"", // 访视 {3} - 阅片人筛选不涉及访视
|
||||
"", // 是否加急 {4}
|
||||
"", // 预留 {5}
|
||||
_systemEmailConfig.SiteUrl // 链接 {6}
|
||||
);
|
||||
|
||||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailNoticeConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ public class UrgentMedicalReviewAddedEventConsumer(
|
|||
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig
|
||||
) : IConsumer<UrgentMedicalReviewAddedEvent>
|
||||
{
|
||||
|
|
@ -48,19 +48,29 @@ public class UrgentMedicalReviewAddedEventConsumer(
|
|||
|
||||
var medicalReviewId = context.Message.MedicalReviewId;
|
||||
|
||||
var medicalReview = await _taskMedicalReviewRepository.Where(x => x.Id == medicalReviewId).Include(x => x.VisitTask).Include(x=>x.MedicalManagerUser).FirstOrDefaultAsync();
|
||||
|
||||
var medicalReview = await _taskMedicalReviewRepository.Where(x => x.Id == medicalReviewId).Include(x => x.VisitTask).Include(x => x.MedicalManagerUser).FirstOrDefaultAsync();
|
||||
|
||||
|
||||
if (medicalReview.MedicalManagerUserId == null)
|
||||
{
|
||||
Console.WriteLine("MIMId为空 ID"+ medicalReviewId);
|
||||
Console.WriteLine("MIMId为空 ID" + medicalReviewId);
|
||||
return;
|
||||
}
|
||||
|
||||
var trialId = medicalReview.TrialId;
|
||||
|
||||
var scenario = context.Message.IsPD ? EmailBusinessScenario.PDVerification_MedicalQC : EmailBusinessScenario.EligibilityVerification_MedicalQC;
|
||||
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (trialEmailConfig != null)
|
||||
{
|
||||
|
||||
|
||||
var criterion = await _readingQuestionCriterionTrialRepository.FirstOrDefaultAsync(x => x.Id == medicalReview.VisitTask.TrialReadingCriterionId);
|
||||
var userinfo = await _userRoleRepository.Where(x => x.Id == medicalReview.MedicalManagerUserId).FirstOrDefaultAsync();
|
||||
|
||||
var taskInfo = await _visitTaskRepository.Where(x => x.Id == medicalReview.VisitTaskId).Include(x=>x.SourceSubjectVisit).Include(x => x.ReadModule).Include(x => x.Subject).FirstNotNullAsync();
|
||||
var taskInfo = await _visitTaskRepository.Where(x => x.Id == medicalReview.VisitTaskId).Include(x => x.SourceSubjectVisit).Include(x => x.ReadModule).Include(x => x.Subject).FirstNotNullAsync();
|
||||
|
||||
|
||||
var visitid = taskInfo.SourceSubjectVisit == null ? taskInfo.ReadModule.SubjectVisitId : taskInfo.SourceSubjectVisitId;
|
||||
|
|
@ -93,7 +103,7 @@ public class UrgentMedicalReviewAddedEventConsumer(
|
|||
new DictionaryDto (){DictionaryCode= "MedicalReviewAuditState",EnumValue=medicalReview.AuditState.GetEnumInt(), },
|
||||
new DictionaryDto (){DictionaryCode= "YesOrNo",EnumValue=taskInfo.IsUrgent.ToString().ToLower(), }
|
||||
}
|
||||
}) ;
|
||||
});
|
||||
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
|
||||
{
|
||||
|
|
@ -116,13 +126,12 @@ public class UrgentMedicalReviewAddedEventConsumer(
|
|||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository,
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
context.Message.IsPD ? EmailBusinessScenario.PDVerification_MedicalQC : EmailBusinessScenario.EligibilityVerification_MedicalQC,
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
|
||||
messageToSend, emailConfigFunc);
|
||||
}
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +144,7 @@ public class UrgentIRRepliedMedicalReviewConsumer(
|
|||
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<UrgentIRRepliedMedicalReview>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
|
@ -151,13 +160,22 @@ public class UrgentIRRepliedMedicalReviewConsumer(
|
|||
|
||||
var medicalReview = await _taskMedicalReviewRepository.Where(x => x.Id == medicalReviewId).Include(x => x.VisitTask).Include(x => x.MedicalManagerUser).FirstOrDefaultAsync();
|
||||
|
||||
|
||||
|
||||
if (medicalReview.MedicalManagerUserId == null)
|
||||
{
|
||||
Console.WriteLine("MIMId为空 ID" + medicalReviewId);
|
||||
return;
|
||||
}
|
||||
var trialId = medicalReview.TrialId;
|
||||
|
||||
var scenario = context.Message.IsPD ? EmailBusinessScenario.PDVerification_ExpeditedMedicalQCResponse : EmailBusinessScenario.EligibilityVerification_ExpeditedMedicalQCResponse;
|
||||
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (trialEmailConfig != null)
|
||||
{
|
||||
|
||||
|
||||
|
||||
var criterion = await _readingQuestionCriterionTrialRepository.FirstOrDefaultAsync(x => x.Id == medicalReview.VisitTask.TrialReadingCriterionId);
|
||||
var userinfo = await _userRoleRepository.Where(x => x.Id == medicalReview.MedicalManagerUserId).FirstOrDefaultAsync();
|
||||
|
||||
|
|
@ -216,14 +234,11 @@ public class UrgentIRRepliedMedicalReviewConsumer(
|
|||
|
||||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository,
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
|
||||
context.Message.IsPD ? EmailBusinessScenario.PDVerification_ExpeditedMedicalQCResponse : EmailBusinessScenario.EligibilityVerification_ExpeditedMedicalQCResponse,
|
||||
|
||||
messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -238,7 +253,7 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
|
|||
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<UrgentMIMRepliedMedicalReview>
|
||||
{
|
||||
|
||||
|
|
@ -254,17 +269,27 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
|
|||
Console.WriteLine("发送(022) 【加急医学反馈】邮件!!!");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
|
||||
var medicalReviewId = context.Message.MedicalReviewId;
|
||||
|
||||
var medicalReview = await _taskMedicalReviewRepository.Where(x => x.Id == medicalReviewId).Include(x => x.VisitTask).Include(x => x.MedicalManagerUser).FirstOrDefaultAsync();
|
||||
|
||||
|
||||
|
||||
if (medicalReview.MedicalManagerUserId == null)
|
||||
{
|
||||
Console.WriteLine("MIMId为空 ID" + medicalReviewId);
|
||||
return;
|
||||
}
|
||||
|
||||
var trialId = medicalReview.TrialId;
|
||||
|
||||
var scenario = EmailBusinessScenario.ExpeditedMedicalQCToIR;
|
||||
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (trialEmailConfig != null)
|
||||
{
|
||||
|
||||
|
||||
var criterion = await _readingQuestionCriterionTrialRepository.FirstOrDefaultAsync(x => x.Id == medicalReview.VisitTask.TrialReadingCriterionId);
|
||||
var taskInfo = await _visitTaskRepository.Where(x => x.Id == medicalReview.VisitTaskId).Include(x => x.SourceSubjectVisit).Include(x => x.ReadModule).Include(x => x.Subject).FirstNotNullAsync();
|
||||
|
||||
|
|
@ -302,11 +327,6 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
|
|||
new DictionaryDto (){DictionaryCode= "IsPass",EnumValue=medicalReview.IsHaveQuestion.ToString().ToLower(), },// 审核结论
|
||||
new DictionaryDto (){DictionaryCode= "AuditAdvice",EnumValue=medicalReview.AuditAdviceEnum.GetEnumInt(), },// 审核建议
|
||||
new DictionaryDto (){DictionaryCode= "YesOrNo",EnumValue=taskInfo.IsUrgent.ToString().ToLower(), }, //是否加急
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -332,13 +352,9 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
|
|||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository,
|
||||
|
||||
EmailBusinessScenario.ExpeditedMedicalQCToIR,
|
||||
|
||||
messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -354,7 +370,7 @@ public class UrgentIRApplyedReReadingConsumer(
|
|||
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<UrgentApplyedReReading>
|
||||
{
|
||||
|
||||
|
|
@ -367,17 +383,28 @@ public class UrgentIRApplyedReReadingConsumer(
|
|||
var visitTaskId = context.Message.VisitTaskId;
|
||||
|
||||
var taskInfo = await _visitTaskRepository.Where(x => x.Id == visitTaskId).Include(x => x.SourceSubjectVisit).Include(x => x.ReadModule).Include(x => x.Subject).FirstNotNullAsync();
|
||||
|
||||
var trialId=taskInfo.TrialId;
|
||||
|
||||
var scenario = context.Message.ReReadingApplyState == ReReadingApplyState.TrialGroupHaveApplyed ? EmailBusinessScenario.ReReadFromPMApproval : EmailBusinessScenario.ReReadFromIRApproval;
|
||||
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (trialEmailConfig != null)
|
||||
{
|
||||
|
||||
|
||||
var criterion = await _readingQuestionCriterionTrialRepository.FirstOrDefaultAsync(x => x.Id == taskInfo.TrialReadingCriterionId);
|
||||
|
||||
|
||||
var doctorInfo = await _userRoleRepository.Where(x => x.Id == taskInfo.DoctorUserId).FirstOrDefaultAsync();
|
||||
|
||||
var trialUser = await _trialUseRoleRepository.Where(x => x.TrialId == taskInfo.TrialId).Include(x => x.UserRole).Select(x=>x.UserRole).ToListAsync();
|
||||
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == taskInfo.TrialId && x.TrialUser.IsDeleted==false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
|
||||
var userinfoList = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.ProjectManager||x.UserTypeEnum==UserTypeEnum.APM).ToList();
|
||||
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.ProjectManager || x.UserTypeEnum == UserTypeEnum.APM).ToList();
|
||||
if (context.Message.ReReadingApplyState == ReReadingApplyState.TrialGroupHaveApplyed)
|
||||
{
|
||||
userinfoList = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.CPM || x.UserTypeEnum == UserTypeEnum.SPM).ToList();
|
||||
userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.CPM || x.UserTypeEnum == UserTypeEnum.SPM).ToList();
|
||||
}
|
||||
|
||||
var visitid = taskInfo.SourceSubjectVisit == null ? taskInfo.ReadModule.SubjectVisitId : taskInfo.SourceSubjectVisitId;
|
||||
|
|
@ -433,13 +460,10 @@ public class UrgentIRApplyedReReadingConsumer(
|
|||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository,
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
context.Message.ReReadingApplyState == ReReadingApplyState.TrialGroupHaveApplyed? EmailBusinessScenario.ReReadFromPMApproval: EmailBusinessScenario.ReReadFromIRApproval,
|
||||
|
||||
messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ using System.Threading.Tasks;
|
|||
namespace IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
|
||||
/// <summary>
|
||||
/// 用户提交 发送邮件 通知SPM 或者PM
|
||||
/// 用户提交 发送邮件 通知SPM 或者PM 项目默认邮件,不用添加到项目中,直接发送的
|
||||
/// </summary>
|
||||
public class UserSiteSurveySubmitedEventConsumer(
|
||||
IRepository<Trial> _trialRepository,
|
||||
|
|
@ -47,7 +47,16 @@ public class UserSiteSurveySubmitedEventConsumer(
|
|||
|
||||
var trialId = siteSurveyInfo.TrialId;
|
||||
|
||||
var trialUserList = await _trialUserRoleRepository.Where(t => t.TrialId == siteSurveyInfo.TrialId)
|
||||
var scenario = _trialSiteSurveyRepository.Where(t => t.Id != trialSiteSurveyId && t.TrialId == siteSurveyInfo.TrialId && t.TrialSiteId == siteSurveyInfo.TrialSiteId).IgnoreQueryFilters().Any() ?
|
||||
EmailBusinessScenario.Approval_UpdateSiteSurvey : EmailBusinessScenario.Approval_SubmitSiteSurvey;
|
||||
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
|
||||
var trialUserList = await _trialUserRoleRepository.Where(t => t.TrialId == siteSurveyInfo.TrialId && t.TrialUser.IsDeleted == false)
|
||||
.Where(t => t.UserRole.UserTypeEnum == UserTypeEnum.SPM || t.UserRole.UserTypeEnum == UserTypeEnum.CPM || t.UserRole.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserRole.UserTypeEnum == UserTypeEnum.APM)
|
||||
.Select(t => new { t.UserRole.FullName, t.UserRole.IdentityUser.EMail, t.UserRole.UserTypeEnum }).ToListAsync();
|
||||
|
||||
|
|
@ -110,12 +119,11 @@ public class UserSiteSurveySubmitedEventConsumer(
|
|||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
var scenario = _trialSiteSurveyRepository.Where(t => t.Id != trialSiteSurveyId && t.TrialId == siteSurveyInfo.TrialId && t.TrialSiteId == siteSurveyInfo.TrialSiteId).IgnoreQueryFilters().Any() ?
|
||||
EmailBusinessScenario.Approval_UpdateSiteSurvey : EmailBusinessScenario.Approval_SubmitSiteSurvey;
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository, scenario, messageToSend, emailConfigFunc);
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -143,9 +151,15 @@ public class SiteSurveySPMSubmitedEventConsumer(
|
|||
|
||||
var trialId = siteSurveyInfo.TrialId;
|
||||
|
||||
var scenario = EmailBusinessScenario.Approval_SubmitSiteSurvey;
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
var messageToSend = new MimeMessage();
|
||||
|
||||
var trialUserList = _trialUserRoleRepository.Where(t => t.TrialId == trialId)
|
||||
var trialUserList = _trialUserRoleRepository.Where(t => t.TrialId == trialId && t.TrialUser.IsDeleted == false)
|
||||
.Where(t => t.UserRole.UserTypeEnum == UserTypeEnum.SPM || t.UserRole.UserTypeEnum == UserTypeEnum.CPM || t.UserRole.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserRole.UserTypeEnum == UserTypeEnum.APM)
|
||||
.Select(t => new { t.UserRole.IdentityUser.EMail, t.UserRole.FullName, t.UserRole.UserTypeEnum }).ToList();
|
||||
|
||||
|
|
@ -177,21 +191,25 @@ public class SiteSurveySPMSubmitedEventConsumer(
|
|||
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo);
|
||||
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
|
||||
toUserName,
|
||||
siteInfo.TrialSiteCode,
|
||||
siteInfo.TrialSiteAliasName,
|
||||
siteInfo.TrialSiteCode, //中心编号
|
||||
siteInfo.TrialSiteAliasName,//中心名称
|
||||
|
||||
siteSurveyInfo.UserName,
|
||||
siteSurveyInfo.Email,
|
||||
siteSurveyInfo.Phone,
|
||||
siteSurveyInfo.UserName, //联系人
|
||||
siteSurveyInfo.Email, //联系邮箱
|
||||
siteSurveyInfo.Phone, //联系电话
|
||||
_systemEmailConfig.SiteUrl
|
||||
);
|
||||
|
||||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository, EmailBusinessScenario.Approval_SubmitSiteSurvey, messageToSend, emailConfigFunc);
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
|
||||
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -216,10 +234,18 @@ public class SiteSurverRejectedEventConsumer(
|
|||
|
||||
var trialSiteSurveyId = context.Message.TrialSiteSurveyId;
|
||||
|
||||
var siteSurveyInfo = _trialSiteSurveyRepository.Where(t => t.Id == trialSiteSurveyId).FirstOrDefault().IfNullThrowException();
|
||||
var siteSurveyInfo = _trialSiteSurveyRepository.Where(t => t.Id == trialSiteSurveyId ,ignoreQueryFilters:true).FirstOrDefault().IfNullThrowException();
|
||||
|
||||
var trialId = siteSurveyInfo.TrialId;
|
||||
|
||||
var scenario = EmailBusinessScenario.SiteSurveyReject;
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
|
||||
|
||||
var messageToSend = new MimeMessage();
|
||||
|
||||
var toUserName = siteSurveyInfo.UserName;
|
||||
|
|
@ -233,7 +259,7 @@ public class SiteSurverRejectedEventConsumer(
|
|||
|
||||
//name = user.FullName;
|
||||
|
||||
var sPMOrCPMList = _trialUserRoleRepository.Where(t => t.TrialId == trialId)
|
||||
var sPMOrCPMList = _trialUserRoleRepository.Where(t => t.TrialId == trialId && t.TrialUser.IsDeleted == false)
|
||||
.Where(t => t.UserRole.UserTypeEnum == UserTypeEnum.SPM || t.UserRole.UserTypeEnum == UserTypeEnum.CPM)
|
||||
.Select(t => new { t.UserRole.IdentityUser.EMail, t.UserRole.FullName, t.UserRole.UserTypeEnum }).ToList();
|
||||
|
||||
|
|
@ -274,25 +300,29 @@ public class SiteSurverRejectedEventConsumer(
|
|||
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
|
||||
{
|
||||
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo);
|
||||
var topicStr = string.Format(input.topicStr, companyName,trialInfo.ResearchProgramNo);
|
||||
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
|
||||
toUserName,
|
||||
trialInfo.TrialCode,
|
||||
trialInfo.ResearchProgramNo,
|
||||
trialInfo.ExperimentName,
|
||||
siteInfo.TrialSiteCode,
|
||||
siteInfo.TrialSiteAliasName,
|
||||
siteSurveyInfo.LatestBackReason,
|
||||
_systemEmailConfig.SiteUrl,
|
||||
|
||||
siteInfo.TrialSiteCode, //中心编号
|
||||
siteInfo.TrialSiteAliasName,//中心名称
|
||||
siteSurveyInfo.LatestBackReason, //驳回原因
|
||||
_systemEmailConfig.SiteUrl, //链接
|
||||
(siteSurveyInfo.State == TrialSiteSurveyEnum.ToSubmit ? "inline - block" : "none")
|
||||
);
|
||||
|
||||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository, EmailBusinessScenario.SiteSurveyReject, messageToSend, emailConfigFunc);
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public class CRCSubmitedAndQCToAuditEventConsumer(
|
|||
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<CRCSubmitedAndQCToAuditEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
|
@ -41,14 +41,23 @@ public class CRCSubmitedAndQCToAuditEventConsumer(
|
|||
|
||||
var subjectVisitId = context.Message.SubjectVisitId;
|
||||
|
||||
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.Subject).FirstOrDefaultAsync();
|
||||
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.Subject).FirstNotNullAsync();
|
||||
|
||||
var trialUser = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
var trialId = subjectVisit.TrialId;
|
||||
|
||||
var userinfoList = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.IQC).ToList();
|
||||
var scenario = context.Message.IsPd ? EmailBusinessScenario.PDVerification_PendingImageQCClaim : EmailBusinessScenario.EligibilityVerification_PendingClaim;
|
||||
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (trialEmailConfig != null)
|
||||
{
|
||||
|
||||
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
|
||||
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.IQC).ToList();
|
||||
|
||||
|
||||
var pmandAPm = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
|
||||
var pmandAPm = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
|
||||
|
||||
|
|
@ -111,13 +120,10 @@ public class CRCSubmitedAndQCToAuditEventConsumer(
|
|||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository,
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
context.Message.IsPd ? EmailBusinessScenario.PDVerification_PendingImageQCClaim : EmailBusinessScenario.EligibilityVerification_PendingClaim,
|
||||
|
||||
messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -134,7 +140,7 @@ public class CRCRepliedQCChallengeEventConsumer(
|
|||
IRepository<QCChallenge> _qCChallengeRepository,
|
||||
IRepository<QCChallengeDialog> _qCChallengeDialogRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<CRCRepliedQCChallengeEvent>
|
||||
{
|
||||
|
||||
|
|
@ -148,18 +154,23 @@ public class CRCRepliedQCChallengeEventConsumer(
|
|||
|
||||
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.Subject).Include(x => x.CurrentActionUser).FirstNotNullAsync();
|
||||
|
||||
var trialId = subjectVisit.TrialId;
|
||||
|
||||
var scenario = context.Message.IsPd ? EmailBusinessScenario.PDVerification_UnderQCQuery : EmailBusinessScenario.EligibilityVerification_Pending;
|
||||
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (trialEmailConfig != null)
|
||||
{
|
||||
|
||||
|
||||
var qCChallengeDialog = await _qCChallengeDialogRepository.Where(x => x.Id == context.Message.QCChallengeDialogId).Include(x => x.QCChallenge).FirstNotNullAsync();
|
||||
|
||||
|
||||
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
|
||||
var trialUser = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
|
||||
|
||||
|
||||
|
||||
var userinfoList = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.IQC).ToList();
|
||||
var pmandAPm = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
|
||||
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.IQC).ToList();
|
||||
var pmandAPm = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
|
||||
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
|
||||
|
|
@ -208,14 +219,10 @@ public class CRCRepliedQCChallengeEventConsumer(
|
|||
|
||||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository,
|
||||
|
||||
context.Message.IsPd ? EmailBusinessScenario.PDVerification_UnderQCQuery : EmailBusinessScenario.EligibilityVerification_Pending,
|
||||
|
||||
messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -231,7 +238,7 @@ public class QCRepliedQCChallengeEventConsumer(
|
|||
IRepository<QCChallenge> _qCChallengeRepository,
|
||||
IRepository<QCChallengeDialog> _qCChallengeDialogRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<QCRepliedQCChallengeEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
|
@ -240,6 +247,20 @@ public class QCRepliedQCChallengeEventConsumer(
|
|||
Console.WriteLine("发送(014,015) 【 QC回复 质控质疑,通知CRC】邮件!!!");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
var subjectVisitId = context.Message.SubjectVisitId;
|
||||
|
||||
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.Subject).FirstNotNullAsync();
|
||||
|
||||
var trialId = subjectVisit.TrialId;
|
||||
|
||||
var scenario = context.Message.IsPd ? EmailBusinessScenario.PDVerification_ImageQuery : EmailBusinessScenario.EligibilityVerification_ImageQuery;
|
||||
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (trialEmailConfig != null)
|
||||
{
|
||||
|
||||
|
||||
string talkContent = string.Empty;
|
||||
var isclose = false;
|
||||
if (context.Message.QCChallengeDialogId != null)
|
||||
|
|
@ -248,22 +269,20 @@ public class QCRepliedQCChallengeEventConsumer(
|
|||
talkContent = qCChallengeDialog.TalkContent;
|
||||
isclose = qCChallengeDialog.QCChallenge.IsClosed;
|
||||
}
|
||||
else if(context.Message.QCChallengeId != null)
|
||||
else if (context.Message.QCChallengeId != null)
|
||||
{
|
||||
var qCChallenge = await _qCChallengeRepository.Where(x => x.Id == context.Message.QCChallengeId.Value).FirstNotNullAsync();
|
||||
talkContent = qCChallenge.Content;
|
||||
isclose = qCChallenge.IsClosed;
|
||||
}
|
||||
|
||||
var subjectVisitId = context.Message.SubjectVisitId;
|
||||
|
||||
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.Subject).FirstNotNullAsync();
|
||||
|
||||
var trialUser = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
|
||||
var userinfoList = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.ClinicalResearchCoordinator).ToList();
|
||||
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.ClinicalResearchCoordinator).ToList();
|
||||
|
||||
var craInfo = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.CRA).FirstOrDefault();
|
||||
var craInfo = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.CRA).FirstOrDefault();
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
|
||||
|
||||
|
|
@ -317,13 +336,11 @@ public class QCRepliedQCChallengeEventConsumer(
|
|||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository,
|
||||
|
||||
context.Message.IsPd ? EmailBusinessScenario.PDVerification_ImageQuery : EmailBusinessScenario.EligibilityVerification_ImageQuery,
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -340,7 +357,7 @@ public class CRCRepliedCheckChallengeEventConsumer(
|
|||
IRepository<QCChallenge> _qCChallengeRepository,
|
||||
IRepository<QCChallengeDialog> _qCChallengeDialogRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<CRCRepliedCheckChallengeEvent>
|
||||
{
|
||||
|
||||
|
|
@ -350,13 +367,25 @@ public class CRCRepliedCheckChallengeEventConsumer(
|
|||
Console.WriteLine("发送(019,020) 【 CRC 回复一致性核查质疑 通知PM】邮件!!!");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
|
||||
|
||||
var subjectVisitId = context.Message.SubjectVisitId;
|
||||
|
||||
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.Subject).FirstNotNullAsync();
|
||||
|
||||
var trialUser = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
var trialId = subjectVisit.TrialId;
|
||||
|
||||
var userinfoList = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
|
||||
var scenario = context.Message.IsPd ? EmailBusinessScenario.PDVerification_UnderDRForPMOrAPM : EmailBusinessScenario.EligibilityVerification_UnderDRForPMOrAPM;
|
||||
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (trialEmailConfig != null)
|
||||
{
|
||||
|
||||
|
||||
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
|
||||
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
|
||||
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
|
||||
|
|
@ -407,13 +436,11 @@ public class CRCRepliedCheckChallengeEventConsumer(
|
|||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository,
|
||||
|
||||
context.Message.IsPd ? EmailBusinessScenario.PDVerification_UnderDRForPMOrAPM : EmailBusinessScenario.EligibilityVerification_UnderDRForPMOrAPM,
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -432,7 +459,7 @@ public class PMRepliedCheckChallengeEventConsumer(
|
|||
IRepository<CheckChallengeDialog> _checkChallengeDialogRepository,
|
||||
IRepository<QCChallengeDialog> _qCChallengeDialogRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<PMRepliedCheckChallengeEvent>
|
||||
{
|
||||
|
||||
|
|
@ -441,22 +468,35 @@ public class PMRepliedCheckChallengeEventConsumer(
|
|||
{
|
||||
Console.WriteLine("发送(016,017) 【 PM 一致性核查 通知CRC】邮件!!!");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
var checkChallengeDialog = await _checkChallengeDialogRepository.Where(x => x.Id == context.Message.CheckChallengeDialogId).FirstNotNullAsync();
|
||||
|
||||
var subjectVisitId = context.Message.SubjectVisitId;
|
||||
|
||||
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.NoneDicomStudyList).Include(x => x.StudyList).Include(x => x.Subject).FirstNotNullAsync();
|
||||
|
||||
var trialUser = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
var trialId = subjectVisit.TrialId;
|
||||
|
||||
var userinfoList = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.ClinicalResearchCoordinator).ToList();
|
||||
|
||||
var craInfo = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.CRA).FirstOrDefault();
|
||||
var scenario = context.Message.IsPd ? EmailBusinessScenario.PDVerification_UnderDR : EmailBusinessScenario.EligibilityVerification_UnderDR;
|
||||
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (trialEmailConfig != null)
|
||||
{
|
||||
|
||||
var checkChallengeDialog = await _checkChallengeDialogRepository.Where(x => x.Id == context.Message.CheckChallengeDialogId).FirstNotNullAsync();
|
||||
|
||||
|
||||
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
|
||||
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.ClinicalResearchCoordinator).ToList();
|
||||
|
||||
var craInfo = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.CRA).FirstOrDefault();
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
|
||||
|
||||
|
||||
List<string> modalities = new List<string>();
|
||||
modalities =subjectVisit.NoneDicomStudyList.Select(t => t.Modality)
|
||||
modalities = subjectVisit.NoneDicomStudyList.Select(t => t.Modality)
|
||||
.Union(subjectVisit.StudyList.Select(k => k.ModalityForEdit)).ToList();
|
||||
|
||||
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
|
||||
|
|
@ -486,8 +526,6 @@ public class PMRepliedCheckChallengeEventConsumer(
|
|||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
|
||||
|
||||
|
||||
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
|
||||
{
|
||||
var subjectCode = subjectVisit.Subject.Code;
|
||||
|
|
@ -508,13 +546,11 @@ public class PMRepliedCheckChallengeEventConsumer(
|
|||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository,
|
||||
|
||||
context.Message.IsPd ? EmailBusinessScenario.PDVerification_UnderDR : EmailBusinessScenario.EligibilityVerification_UnderDR,
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -531,7 +567,7 @@ public class CheckStateChangedToAuditEventConsumer(
|
|||
IRepository<QCChallenge> _qCChallengeRepository,
|
||||
IRepository<QCChallengeDialog> _qCChallengeDialogRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<CheckStateChangedToAuditEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
|
@ -540,13 +576,25 @@ public class CheckStateChangedToAuditEventConsumer(
|
|||
Console.WriteLine("发送(018,030) 【通知PM 进行一致性核查】邮件!!!");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
|
||||
var subjectVisitId = context.Message.SubjectVisitId;
|
||||
|
||||
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.Subject).FirstNotNullAsync();
|
||||
|
||||
var trialUser = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
var trialId = subjectVisit.TrialId;
|
||||
|
||||
var userinfoList = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
|
||||
var scenario = context.Message.IsPd ? EmailBusinessScenario.PDVerification_DR : EmailBusinessScenario.EligibilityVerification_DR;
|
||||
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (trialEmailConfig != null)
|
||||
{
|
||||
|
||||
|
||||
|
||||
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
|
||||
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
|
||||
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
|
||||
|
|
@ -597,13 +645,11 @@ public class CheckStateChangedToAuditEventConsumer(
|
|||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository,
|
||||
|
||||
context.Message.IsPd ? EmailBusinessScenario.PDVerification_DR : EmailBusinessScenario.EligibilityVerification_DR,
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -619,7 +665,7 @@ public class QCClaimTaskEventConsumer(
|
|||
IRepository<QCChallenge> _qCChallengeRepository,
|
||||
IRepository<QCChallengeDialog> _qCChallengeDialogRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<QCClaimTaskEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
|
@ -628,24 +674,29 @@ public class QCClaimTaskEventConsumer(
|
|||
Console.WriteLine("发送(Code007,008) 【QC 领取了质控任务】邮件!!!");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
|
||||
|
||||
var subjectVisitId = context.Message.SubjectVisitId;
|
||||
|
||||
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.Subject).Include(x => x.CurrentActionUser).FirstNotNullAsync();
|
||||
|
||||
var trialId = subjectVisit.TrialId;
|
||||
|
||||
var scenario = context.Message.IsPd ? EmailBusinessScenario.PDVerification_PendingImageQC : EmailBusinessScenario.EligibilityVerification_PendingImageQC;
|
||||
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (trialEmailConfig != null)
|
||||
{
|
||||
|
||||
|
||||
var subjectVisitList = await _subjectVisitRepository.Where(x => x.CurrentActionUserId == context.Message.CurrentActionUserId && x.TrialId == subjectVisit.TrialId).Include(x => x.Subject).ToListAsync();
|
||||
|
||||
|
||||
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
|
||||
|
||||
|
||||
var trialUser = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var pmandAPm = trialUser.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
|
||||
var pmandAPm = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
|
||||
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
|
||||
|
|
@ -703,12 +754,10 @@ public class QCClaimTaskEventConsumer(
|
|||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository,
|
||||
|
||||
context.Message.IsPd ? EmailBusinessScenario.PDVerification_PendingImageQC : EmailBusinessScenario.EligibilityVerification_PendingImageQC,
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
using DocumentFormat.OpenXml;
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
using IRaCIS.Core.Application.Service.Reading.Dto;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
|
|
@ -25,9 +27,10 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
|
|||
public class UrgentIRUnReadTaskRecurringEventConsumer(
|
||||
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<Trial> _trialRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<TrialUserRole> _trialUserRoleRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<UrgentIRUnReadTaskRecurringEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
|
@ -35,9 +38,16 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
|
|||
public async Task Consume(ConsumeContext<UrgentIRUnReadTaskRecurringEvent> context)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
var trialId = context.Message.TrialId;
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
|
||||
var scenario = EmailBusinessScenario.ExpeditedReading;
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (trialEmailConfig != null)
|
||||
{
|
||||
|
||||
|
||||
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
|
||||
{
|
||||
DictionaryRepository = _dictionaryRepository,
|
||||
|
|
@ -152,9 +162,9 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
|
|||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(_emailNoticeConfigrepository, EmailBusinessScenario.ExpeditedReading, messageToSend, emailConfigFunc);
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
|
||||
//处理标记已通知的任务
|
||||
|
||||
|
|
@ -168,6 +178,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
|
|||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -28,12 +28,13 @@ public static class OldRecurringEmailHelper
|
|||
|
||||
Guid trialId, EmailBusinessScenario businessScenario,
|
||||
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc,
|
||||
Guid? trialSiteId = null, Guid? trialReadingCriterionId = null)
|
||||
Guid? trialSiteId = null)
|
||||
{
|
||||
//找到配置
|
||||
var trialEmailConfig = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.TrialReadingCriterionId == trialReadingCriterionId && t.BusinessScenarioEnum == businessScenario, ignoreQueryFilters: true)
|
||||
var trialEmailConfig = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == businessScenario, ignoreQueryFilters: true)
|
||||
.Include(t => t.TrialEmailNoticeUserList).Include(t => t.TrialEmailBlackUserList).FirstOrDefaultAsync();
|
||||
|
||||
var trialInfo=await _trialRepository.Where(t=>t.Id== trialId).FirstOrDefaultAsync();
|
||||
|
||||
if (trialEmailConfig == null || trialEmailConfig.IsAutoSend == false || trialEmailConfig.IsEnable == false)
|
||||
{
|
||||
|
|
@ -162,7 +163,7 @@ public static class OldRecurringEmailHelper
|
|||
if (sendEmailConfig != null)
|
||||
{
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(sendEmailConfig);
|
||||
await SendEmailHelper.SendEmailAsync(sendEmailConfig, trialInfo);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ using System.Threading.Tasks;
|
|||
namespace IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
|
||||
|
||||
|
||||
//项目手动选择 周期性邮件
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -25,9 +25,11 @@ public class QCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRepo
|
|||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
|
||||
IRepository<TrialUserRole> _trialUserRoleRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
|
||||
IRepository<TrialSiteUserRole> _trialSiteUserRoleRepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig) : IConsumer<QCImageQuestionRecurringEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
public async Task Consume(ConsumeContext<QCImageQuestionRecurringEvent> context)
|
||||
{
|
||||
var trialId = context.Message.TrialId;
|
||||
|
|
@ -64,8 +66,9 @@ public class QCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRepo
|
|||
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
|
||||
{
|
||||
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, trialInfo.ResearchProgramNo);
|
||||
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
|
||||
user.FullName, DateTime.Now, sendStat.ToBeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
|
||||
var htmlContent = isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN;
|
||||
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, htmlContent),
|
||||
user.FullName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), sendStat.ToBeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
|
||||
|
||||
return (topicStr, htmlBodyStr, isEn_us, userId);
|
||||
};
|
||||
|
|
@ -91,8 +94,10 @@ public class CRCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRep
|
|||
IRepository<TrialUserRole> _trialUserRoleRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<TrialSiteUserRole> _trialSiteUserRoleRepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
|
||||
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig) : IConsumer<CRCImageQuestionRecurringEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
public async Task Consume(ConsumeContext<CRCImageQuestionRecurringEvent> context)
|
||||
{
|
||||
var trialId = context.Message.TrialId;
|
||||
|
|
@ -131,9 +136,10 @@ public class CRCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRep
|
|||
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
|
||||
{
|
||||
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, trialInfo.ResearchProgramNo);
|
||||
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
|
||||
var htmlContent = isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN;
|
||||
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, htmlContent),
|
||||
|
||||
user.FullName, DateTime.Now, sendStat.ToBeDealedCount - sendStat.ReUploadTobeDealedCount, sendStat.ReUploadTobeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
|
||||
user.FullName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), sendStat.ToBeDealedCount - sendStat.ReUploadTobeDealedCount, sendStat.ReUploadTobeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
|
||||
|
||||
|
||||
return (topicStr, htmlBodyStr, false, userId);
|
||||
|
|
@ -158,8 +164,11 @@ public class ImageQCRecurringEventConsumer(IRepository<Trial> _trialRepository,
|
|||
IRepository<TrialUserRole> _trialUserRoleRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<TrialSiteUserRole> _trialSiteUserRoleRepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
|
||||
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig) : IConsumer<ImageQCRecurringEvent>
|
||||
{
|
||||
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
public async Task Consume(ConsumeContext<ImageQCRecurringEvent> context)
|
||||
{
|
||||
var trialId=context.Message.TrialId;
|
||||
|
|
@ -199,9 +208,12 @@ public class ImageQCRecurringEventConsumer(IRepository<Trial> _trialRepository,
|
|||
|
||||
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
|
||||
{
|
||||
|
||||
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, trialInfo.ResearchProgramNo);
|
||||
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
|
||||
user.FullName, DateTime.Now, sendStat.ToBeClaimedCount, sendStat.ToBeReviewedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
|
||||
|
||||
var htmlContent = isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN;
|
||||
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, htmlContent),
|
||||
user.FullName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), sendStat.ToBeClaimedCount, sendStat.ToBeReviewedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
|
||||
|
||||
return (topicStr, htmlBodyStr, false, userId);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -37,4 +37,45 @@ public class UrgentIRUnReadTaskRecurringEvent : DomainEvent
|
|||
public Guid TrialId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定时提醒
|
||||
/// </summary>
|
||||
public class SystemDocumentErverDayEvent : DomainEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class SystemDocumentPublishEvent : DomainEvent
|
||||
{
|
||||
public List<Guid> Ids { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 新增的需要发送邮件的用户角色ID列表
|
||||
/// 如果为null或空,则发送给所有相关角色
|
||||
/// </summary>
|
||||
public List<Guid> NewUserTypeIds { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 定时提醒
|
||||
/// </summary>
|
||||
public class TrialDocumentErverDayEvent : DomainEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class TrialDocumentPublishEvent : DomainEvent
|
||||
{
|
||||
public List<Guid> Ids { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 新增的需要发送邮件的用户角色ID列表
|
||||
/// 如果为null或空,则发送给所有相关角色
|
||||
/// </summary>
|
||||
public List<Guid> NewUserTypeIds { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,277 @@
|
|||
using DocumentFormat.OpenXml;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
using IRaCIS.Core.Application.Service.Reading.Dto;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reactive.Joins;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.MassTransit.Recurring
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 定时过期提醒
|
||||
/// </summary>
|
||||
public class SystemDocumentErverDayEventConsumer(
|
||||
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<SystemDocument> _systemDocumentRepository,
|
||||
IRepository<IdentityUser> _identityUserRepository,
|
||||
IRepository<SystemDocConfirmedIdentityUser> _systemDocConfirmedUserRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<TrialUserRole> _trialUserRoleRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<SystemDocumentErverDayEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
||||
public async Task Consume(ConsumeContext<SystemDocumentErverDayEvent> context)
|
||||
{
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
Console.WriteLine("发送定时过期提醒");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
var systemDocQuery =
|
||||
from sysDoc in _systemDocumentRepository.AsQueryable(false)
|
||||
from identityUser in _identityUserRepository.AsQueryable(false).Where(t => t.UserRoleList.Where(t => t.IsUserRoleDisabled == false).Any(t => sysDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
|
||||
join confirmc in _systemDocConfirmedUserRepository.Where() on new { ConfirmUserId = identityUser.Id, SystemDocumentId = sysDoc.Id } equals new { confirmc.ConfirmUserId, confirmc.SystemDocumentId } into cc
|
||||
from confirm in cc.DefaultIfEmpty()
|
||||
select new UnionDocumentWithConfirmInfoView()
|
||||
{
|
||||
IsSystemDoc = true,
|
||||
Id = sysDoc.Id,
|
||||
CreateTime = sysDoc.CreateTime,
|
||||
IsDeleted = sysDoc.IsDeleted,
|
||||
SignViewMinimumMinutes = sysDoc.SignViewMinimumMinutes,
|
||||
Name = sysDoc.Name,
|
||||
Path = sysDoc.Path,
|
||||
FileTypeId = sysDoc.FileTypeId,
|
||||
UpdateTime = sysDoc.UpdateTime,
|
||||
ConfirmUserId = identityUser.Id,
|
||||
ConfirmTime = confirm.ConfirmTime,
|
||||
UserCreateTime = identityUser.CreateTime,
|
||||
CurrentStaffTrainDays = sysDoc.CurrentStaffTrainDays,
|
||||
NewStaffTrainDays = sysDoc.NewStaffTrainDays,
|
||||
RealName = identityUser.FullName,
|
||||
UserName = identityUser.UserName,
|
||||
|
||||
|
||||
IsNeedSendEmial = identityUser.IsZhiZhun || (!identityUser.IsZhiZhun && sysDoc.DocUserSignType == DocUserSignType.InnerAndOuter),
|
||||
FullFilePath = sysDoc.Path
|
||||
};
|
||||
var datalist = await systemDocQuery.IgnoreQueryFilters().Where(t => t.IsDeleted == false && t.ConfirmTime == null)
|
||||
.ToListAsync();
|
||||
datalist = datalist.Where(x => x.SuggestFinishTime != null && x.SuggestFinishTime.Value.Date == DateTime.Now.Date)
|
||||
.Where(x => x.IsNeedSendEmial).ToList();
|
||||
var confirmUserIdList = datalist.Select(t => t.ConfirmUserId).Distinct().ToList();
|
||||
var userinfoList = await _identityUserRepository.Where(x => confirmUserIdList.Contains(x.Id)).ToListAsync();
|
||||
|
||||
Console.WriteLine("发送定时过期提醒:人员数量" + userinfoList.Count);
|
||||
int index = 1;
|
||||
foreach (var userinfo in userinfoList)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"{index}发送定时过期提醒,邮箱:{userinfo.EMail},姓名{userinfo.UserName}");
|
||||
index++;
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
|
||||
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
|
||||
{
|
||||
var topicStr = string.Format(input.topicStr, companyName);
|
||||
|
||||
var htmlBodyStr = string.Format(
|
||||
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
|
||||
userinfo.UserName, // 用户名 {0}
|
||||
_systemEmailConfig.SiteUrl
|
||||
);
|
||||
|
||||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
var scenario = EmailBusinessScenario.GeneralTraining_ExpirationNotification;
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生效通知
|
||||
/// </summary>
|
||||
public class SystemDocumentPublishEventConsumer(
|
||||
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<SystemDocument> _systemDocumentRepository,
|
||||
IRepository<IdentityUser> _identityUserRepository,
|
||||
IRepository<SystemDocConfirmedIdentityUser> _systemDocConfirmedUserRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<TrialUserRole> _trialUserRoleRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<SystemDocumentPublishEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
||||
public async Task Consume(ConsumeContext<SystemDocumentPublishEvent> context)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
// 记录是否只发送给新增角色的日志
|
||||
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
|
||||
{
|
||||
Console.WriteLine($"只发送给新增的角色,角色数量: {context.Message.NewUserTypeIds.Count}");
|
||||
}
|
||||
// 构建查询
|
||||
IQueryable<UnionDocumentWithConfirmInfoView> systemDocQuery;
|
||||
|
||||
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
|
||||
{
|
||||
// 只查询新增角色的用户
|
||||
systemDocQuery =
|
||||
from sysDoc in _systemDocumentRepository.AsQueryable(false).Where(x => context.Message.Ids.Contains(x.Id))
|
||||
from identityUser in _identityUserRepository.AsQueryable(false)
|
||||
.Where(t => t.Status == UserStateEnum.Enable &&
|
||||
t.UserRoleList.Where(t => t.IsUserRoleDisabled == false)
|
||||
.Any(t => context.Message.NewUserTypeIds.Contains(t.UserTypeId) &&
|
||||
sysDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
|
||||
select new UnionDocumentWithConfirmInfoView()
|
||||
{
|
||||
IsSystemDoc = true,
|
||||
Id = sysDoc.Id,
|
||||
CreateTime = sysDoc.CreateTime,
|
||||
IsDeleted = sysDoc.IsDeleted,
|
||||
SignViewMinimumMinutes = sysDoc.SignViewMinimumMinutes,
|
||||
Name = sysDoc.Name,
|
||||
Path = sysDoc.Path,
|
||||
FileTypeId = sysDoc.FileTypeId,
|
||||
UpdateTime = sysDoc.UpdateTime,
|
||||
ConfirmUserId = identityUser.Id,
|
||||
RealName = identityUser.FullName,
|
||||
UserName = identityUser.UserName,
|
||||
IsNeedSendEmial = identityUser.IsZhiZhun || (!identityUser.IsZhiZhun && sysDoc.DocUserSignType == DocUserSignType.InnerAndOuter),
|
||||
FullFilePath = sysDoc.Path
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// 查询所有相关角色的用户
|
||||
systemDocQuery =
|
||||
from sysDoc in _systemDocumentRepository.AsQueryable(false).Where(x => context.Message.Ids.Contains(x.Id))
|
||||
from identityUser in _identityUserRepository.AsQueryable(false)
|
||||
.Where(t => t.Status == UserStateEnum.Enable &&
|
||||
t.UserRoleList.Where(t => t.IsUserRoleDisabled == false)
|
||||
.Any(t => sysDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
|
||||
select new UnionDocumentWithConfirmInfoView()
|
||||
{
|
||||
IsSystemDoc = true,
|
||||
Id = sysDoc.Id,
|
||||
CreateTime = sysDoc.CreateTime,
|
||||
IsDeleted = sysDoc.IsDeleted,
|
||||
SignViewMinimumMinutes = sysDoc.SignViewMinimumMinutes,
|
||||
Name = sysDoc.Name,
|
||||
Path = sysDoc.Path,
|
||||
FileTypeId = sysDoc.FileTypeId,
|
||||
UpdateTime = sysDoc.UpdateTime,
|
||||
ConfirmUserId = identityUser.Id,
|
||||
RealName = identityUser.FullName,
|
||||
UserName = identityUser.UserName,
|
||||
IsNeedSendEmial = identityUser.IsZhiZhun || (!identityUser.IsZhiZhun && sysDoc.DocUserSignType == DocUserSignType.InnerAndOuter),
|
||||
FullFilePath = sysDoc.Path
|
||||
};
|
||||
}
|
||||
var datalist = await systemDocQuery.IgnoreQueryFilters().Where(x => x.IsNeedSendEmial).ToListAsync();
|
||||
|
||||
var confirmUserIdList = datalist.Select(t => t.ConfirmUserId).Distinct().ToList();
|
||||
var userinfoList = await _identityUserRepository.Where(x => confirmUserIdList.Contains(x.Id)).ToListAsync();
|
||||
int index = 1;
|
||||
foreach (var userinfo in userinfoList)
|
||||
{
|
||||
string msg = $"{index}生效通知,邮箱:{userinfo.EMail},姓名{userinfo.UserName},";
|
||||
index++;
|
||||
try
|
||||
{
|
||||
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
|
||||
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
|
||||
{
|
||||
var topicStr = string.Format(input.topicStr, companyName);
|
||||
|
||||
var htmlBodyStr = string.Format(
|
||||
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
|
||||
userinfo.UserName, // 用户名 {0}
|
||||
_systemEmailConfig.SiteUrl
|
||||
);
|
||||
|
||||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
var scenario = EmailBusinessScenario.GeneralTraining_EffectiveNotification;
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
msg += "发送成功";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
msg += "发送失败";
|
||||
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine(msg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
using DocumentFormat.OpenXml;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
using IRaCIS.Core.Application.Service.Reading.Dto;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using System.Reactive.Joins;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.MassTransit.Recurring
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 定时过期提醒
|
||||
/// </summary>
|
||||
public class TrialDocumentErverDayEventConsumer(
|
||||
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<SystemDocument> _systemDocumentRepository,
|
||||
IRepository<IdentityUser> _identityUserRepository,
|
||||
IRepository<SystemDocConfirmedIdentityUser> _systemDocConfirmedUserRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<TrialUserRole> _trialUserRoleRepository, IRepository<TrialDocument> _trialDocumentRepository,
|
||||
IRepository<Trial> _trialRepository,
|
||||
ISystemDocumentService _systemDocumentService,
|
||||
IRepository<SystemDocNeedConfirmedUserType> _systemDocNeedConfirmedUserTypeRepository,
|
||||
IRepository<TrialDocNeedConfirmedUserType> _trialDocNeedConfirmedUserTypeRepository,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
IRepository<TrialIdentityUser> _trialIdentityUserRepository,
|
||||
IRepository<TrialDocConfirmedIdentityUser> _trialDocConfirmedUserRepository,
|
||||
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<TrialDocumentErverDayEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
||||
public async Task Consume(ConsumeContext<TrialDocumentErverDayEvent> context)
|
||||
{
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
Console.WriteLine("发送定时项目过期提醒");
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
var trialDocQuery =
|
||||
from trialDoc in _trialDocumentRepository.AsQueryable(true)
|
||||
|
||||
join trialIdentityUser in _trialIdentityUserRepository.Where(x => x.IsDeleted == false) on trialDoc.TrialId equals trialIdentityUser.TrialId
|
||||
join trialUserRole in _trialUserRoleRepository.Where(x => x.IsDeleted == false) on trialIdentityUser.Id equals trialUserRole.TrialUserId
|
||||
join identityUser in _identityUserRepository.AsQueryable(false).Where(u => u.Status == UserStateEnum.Enable)
|
||||
on trialIdentityUser.IdentityUserId equals identityUser.Id
|
||||
|
||||
|
||||
join confirm in _trialDocConfirmedUserRepository.Where() on
|
||||
new { trialIdentityUser.IdentityUserId, TrialDocumentId = trialDoc.Id } equals new { IdentityUserId = confirm.ConfirmUserId, confirm.TrialDocumentId } into cc
|
||||
|
||||
from confirm in cc.DefaultIfEmpty()
|
||||
where trialIdentityUser.TrialUserRoleList.Any(ur => !ur.IsDeleted && trialDoc.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == ur.UserRole.UserTypeId))
|
||||
select new TrialSignDocView()
|
||||
{
|
||||
TrialCode = trialDoc.Trial.TrialCode,
|
||||
ResearchProgramNo = trialDoc.Trial.ResearchProgramNo,
|
||||
ExperimentName = trialDoc.Trial.ExperimentName,
|
||||
CurrentStaffTrainDays = trialDoc.CurrentStaffTrainDays,
|
||||
NewStaffTrainDays = trialDoc.NewStaffTrainDays,
|
||||
Id = trialDoc.Id,
|
||||
IsSystemDoc = false,
|
||||
CreateTime = trialDoc.CreateTime,
|
||||
FullFilePath = trialDoc.Path,
|
||||
IsDeleted = trialDoc.IsDeleted,
|
||||
Name = trialDoc.Name,
|
||||
Path = trialDoc.Path,
|
||||
FileTypeId = trialDoc.FileTypeId,
|
||||
UpdateTime = trialDoc.UpdateTime,
|
||||
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
|
||||
//IsConfirmed = confirm.ConfirmTime != null,
|
||||
ConfirmUserId = identityUser.Id,
|
||||
ConfirmTime = confirm.ConfirmTime,
|
||||
RealName = trialIdentityUser.IdentityUser.FullName,
|
||||
UserName = trialIdentityUser.IdentityUser.UserName,
|
||||
UserCreateTime= trialIdentityUser.CreateTime,
|
||||
IdentityUserTypeList = trialIdentityUser.TrialUserRoleList.Select(t => t.UserRole.UserTypeRole.UserTypeShortName).ToList(),
|
||||
|
||||
DocNeedSignUserTypeList = trialDoc.NeedConfirmedUserTypeList.Select(t => t.UserTypeRole.UserTypeShortName).ToList(),
|
||||
};
|
||||
var datalist = await trialDocQuery.IgnoreQueryFilters().Where(t => t.IsDeleted == false && t.ConfirmTime == null&&t.ConfirmTime==null)
|
||||
.ToListAsync();
|
||||
datalist = datalist.Where(x => x.SuggestFinishTime != null && x.SuggestFinishTime.Value.Date == DateTime.Now.Date)
|
||||
.ToList();
|
||||
var confirmUserIdList = datalist.Select(t => t.ConfirmUserId).Distinct().ToList();
|
||||
var userinfoList = await _identityUserRepository.Where(x => confirmUserIdList.Contains(x.Id)).ToListAsync();
|
||||
|
||||
Console.WriteLine("发送定时项目过期提醒:人员数量" + userinfoList.Count);
|
||||
int index = 1;
|
||||
foreach (var userinfo in userinfoList)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"{index}发送定时过期提醒,邮箱:{userinfo.EMail},姓名{userinfo.UserName}");
|
||||
index++;
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
|
||||
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
|
||||
{
|
||||
var topicStr = string.Format(input.topicStr, companyName);
|
||||
|
||||
var htmlBodyStr = string.Format(
|
||||
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
|
||||
userinfo.UserName, // 用户名 {0}
|
||||
_systemEmailConfig.SiteUrl
|
||||
);
|
||||
|
||||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
var scenario = EmailBusinessScenario.TrialTraining_ExpirationNotification;
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生效通知
|
||||
/// </summary>
|
||||
public class TrialDocumentPublishEventConsumer(
|
||||
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<TrialDocument> _trialDocumentRepository,
|
||||
IRepository<IdentityUser> _identityUserRepository,
|
||||
IRepository<Trial> _trialRepository,
|
||||
IRepository<TrialIdentityUser> _trialIdentityUserRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<TrialUserRole> _trialUserRoleRepository,
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<TrialDocumentPublishEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
||||
public async Task Consume(ConsumeContext<TrialDocumentPublishEvent> context)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
// 记录是否只发送给新增角色的日志
|
||||
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
|
||||
{
|
||||
Console.WriteLine($"只发送给新增项目的角色,角色数量: {context.Message.NewUserTypeIds.Count}");
|
||||
}
|
||||
// 构建查询
|
||||
IQueryable<UnionDocumentWithConfirmInfoView> systemDocQuery;
|
||||
|
||||
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
|
||||
{
|
||||
// 只查询新增角色的用户
|
||||
systemDocQuery =
|
||||
from trialDoc in _trialDocumentRepository.AsQueryable(false).Where(x => context.Message.Ids.Contains(x.Id))
|
||||
join trialIdentityUser in _trialIdentityUserRepository.Where(x=>x.IsDeleted==false) on trialDoc.TrialId equals trialIdentityUser.TrialId
|
||||
join trialUserRole in _trialUserRoleRepository.Where(x => x.IsDeleted == false) on trialIdentityUser.Id equals trialUserRole.TrialUserId
|
||||
join identityUser in _identityUserRepository.AsQueryable(false).Where(u => u.Status == UserStateEnum.Enable)
|
||||
on trialIdentityUser.IdentityUserId equals identityUser.Id
|
||||
where trialIdentityUser.TrialUserRoleList.Any(ur => !ur.IsDeleted && context.Message.NewUserTypeIds.Contains(ur.UserRole.UserTypeId) && trialDoc.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == ur.UserRole.UserTypeId))
|
||||
|
||||
select new UnionDocumentWithConfirmInfoView()
|
||||
{
|
||||
IsSystemDoc = true,
|
||||
Id = trialDoc.Id,
|
||||
TrialId= trialDoc.TrialId,
|
||||
CreateTime = trialDoc.CreateTime,
|
||||
IsDeleted = trialDoc.IsDeleted,
|
||||
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
|
||||
Name = trialDoc.Name,
|
||||
Path = trialDoc.Path,
|
||||
FileTypeId = trialDoc.FileTypeId,
|
||||
UpdateTime = trialDoc.UpdateTime,
|
||||
ConfirmUserId = identityUser.Id,
|
||||
RealName = identityUser.FullName,
|
||||
UserName = identityUser.UserName,
|
||||
IsNeedSendEmial = identityUser.IsZhiZhun,
|
||||
FullFilePath = trialDoc.Path
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// 查询所有相关角色的用户
|
||||
systemDocQuery =
|
||||
from trialDoc in _trialDocumentRepository.AsQueryable(false).Where(x => context.Message.Ids.Contains(x.Id))
|
||||
join trialIdentityUser in _trialIdentityUserRepository.Where(x => x.IsDeleted == false) on trialDoc.TrialId equals trialIdentityUser.TrialId
|
||||
join trialUserRole in _trialUserRoleRepository.Where(x=>x.IsDeleted==false) on trialIdentityUser.Id equals trialUserRole.TrialUserId
|
||||
|
||||
join trial in _trialRepository.AsQueryable(false) on trialDoc.TrialId equals trial.Id
|
||||
join identityUser in _identityUserRepository.AsQueryable(false).Where(u => u.Status == UserStateEnum.Enable)
|
||||
on trialIdentityUser.IdentityUserId equals identityUser.Id
|
||||
where trialIdentityUser.TrialUserRoleList.Any(ur => !ur.IsDeleted &&trialDoc.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == ur.UserRole.UserTypeId))
|
||||
|
||||
|
||||
select new UnionDocumentWithConfirmInfoView()
|
||||
{
|
||||
IsSystemDoc = false,
|
||||
Id = trialDoc.Id,
|
||||
TrialId = trialDoc.TrialId,
|
||||
EmailFromName =trial.EmailFromName,
|
||||
CreateTime = trialDoc.CreateTime,
|
||||
IsDeleted = trialDoc.IsDeleted,
|
||||
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
|
||||
Name = trialDoc.Name,
|
||||
Path = trialDoc.Path,
|
||||
FileTypeId = trialDoc.FileTypeId,
|
||||
UpdateTime = trialDoc.UpdateTime,
|
||||
ConfirmUserId = identityUser.Id,
|
||||
RealName = identityUser.FullName,
|
||||
UserName = identityUser.UserName,
|
||||
IsNeedSendEmial = identityUser.IsZhiZhun ,
|
||||
FullFilePath = trialDoc.Path
|
||||
};
|
||||
}
|
||||
|
||||
var datalist = await systemDocQuery.IgnoreQueryFilters().ToListAsync();
|
||||
|
||||
var confirmUserIdList = datalist.Select(t => t.ConfirmUserId).Distinct().ToList();
|
||||
var userinfoList = await _identityUserRepository.Where(x => confirmUserIdList.Contains(x.Id)).ToListAsync();
|
||||
int index = 1;
|
||||
foreach (var userinfo in userinfoList)
|
||||
{
|
||||
string msg = $"{index}项目生效通知,邮箱:{userinfo.EMail},姓名{userinfo.UserName},";
|
||||
index++;
|
||||
try
|
||||
{
|
||||
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
|
||||
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
|
||||
{
|
||||
var topicStr = string.Format(input.topicStr, companyName);
|
||||
|
||||
var htmlBodyStr = string.Format(
|
||||
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
|
||||
userinfo.UserName, // 用户名 {0}
|
||||
_systemEmailConfig.SiteUrl
|
||||
);
|
||||
|
||||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
var scenario = EmailBusinessScenario.TrialTraining_EffectiveNotification;
|
||||
|
||||
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsEnable).FirstOrDefault();
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
var trial = datalist.Where(x => x.ConfirmUserId == userinfo.Id).FirstOrDefault();
|
||||
|
||||
var trialInfo = await _trialRepository.Where(x=>x.Id==trial.TrialId).FirstNotNullAsync();
|
||||
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
msg += "发送成功";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
msg += "发送失败";
|
||||
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine(msg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -71,6 +71,8 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
|
||||
public bool IsPMSetBack { get; set; }
|
||||
|
||||
public bool IsSubjectQuit { get; set; }
|
||||
|
||||
#region 标准配置
|
||||
public Guid TrialReadingCriterionId { get; set; }
|
||||
|
||||
|
|
@ -214,6 +216,10 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
/// </summary>
|
||||
public bool IsManualGeneration { get; set; }
|
||||
//public bool IsAfterConvertedTask { get; set; }
|
||||
|
||||
public string PMBackReason { get; set; }
|
||||
|
||||
public int? RandomOrder { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -234,6 +240,7 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
//public string ReReadingOriginalTaskCode { get; set; }
|
||||
|
||||
|
||||
public string ApplicantName { get; set; }
|
||||
|
||||
public Guid Id { get; set; }
|
||||
|
||||
|
|
@ -339,6 +346,16 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
|
||||
public int UrgentCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否存在未处理的反馈
|
||||
/// </summary>
|
||||
public bool IsExistUnprocessedFeedback
|
||||
{
|
||||
get
|
||||
{
|
||||
return UnReadCanReadTaskList.Any(t => t.IsExistUnprocessedFeedback);
|
||||
}
|
||||
}
|
||||
|
||||
public List<IRUnreadTaskView> UnReadCanReadTaskList { get; set; } = new List<IRUnreadTaskView>();
|
||||
|
||||
|
|
@ -356,6 +373,11 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
/// </summary>
|
||||
public bool IsAnalysisCreate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否存在未处理的反馈
|
||||
/// </summary>
|
||||
public bool IsExistUnprocessedFeedback { get; set; }
|
||||
|
||||
public bool IsUrgent { get; set; }
|
||||
|
||||
public decimal VisitNum { get; set; }
|
||||
|
|
@ -465,6 +487,11 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
public string? RequestReReadingReason { get; set; }
|
||||
|
||||
public ExportResult? ReadingExportType { get; set; }
|
||||
|
||||
public int? RandomOrder { get; set; }
|
||||
public bool? IsRandomOrderList { get; set; }
|
||||
|
||||
public CriterionType? CriterionType { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -891,6 +918,30 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
CancelAssign = 4,
|
||||
}
|
||||
|
||||
|
||||
public class SetRandomTaskOrderCommand
|
||||
{
|
||||
[NotDefault]
|
||||
public Guid TrialId { get; set; }
|
||||
|
||||
[NotDefault]
|
||||
public Guid TrialReadingCriterionId { get; set; }
|
||||
|
||||
[NotDefault]
|
||||
public Guid DoctorUserId { get; set; }
|
||||
|
||||
public bool IsAutoSet { get; set; }
|
||||
|
||||
public List<VisitTaskOrderCommand> SetList { get; set; }
|
||||
}
|
||||
|
||||
public class VisitTaskOrderCommand
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public int? RandomOrder { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Filter;
|
||||
using IRaCIS.Core.Application.Interfaces;
|
||||
using IRaCIS.Core.Application.ViewModel;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
|
@ -46,7 +48,7 @@ namespace IRaCIS.Core.Application.Service
|
|||
|
||||
|
||||
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
public async Task<IResponseOutput> AddOrUpdateTaskAllocationRule(TaskAllocationRuleAddOrEdit addOrEditTaskAllocationRule)
|
||||
{
|
||||
|
|
@ -77,7 +79,7 @@ namespace IRaCIS.Core.Application.Service
|
|||
|
||||
}
|
||||
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
[HttpDelete("{taskAllocationRuleId:guid}")]
|
||||
public async Task<IResponseOutput> DeleteTaskAllocationRule(Guid taskAllocationRuleId)
|
||||
|
|
@ -123,7 +125,7 @@ namespace IRaCIS.Core.Application.Service
|
|||
[HttpGet("{trialId:guid}")]
|
||||
public async Task<List<TrialDoctorUserSelectView>> GetDoctorUserSelectList(Guid trialId, [FromServices] IRepository<Enroll> _enrollRepository)
|
||||
{
|
||||
var query = from enroll in _enrollRepository.Where(t => t.TrialId == trialId && t.EnrollStatus >= EnrollStatus.ConfirmIntoGroup)
|
||||
var query = from enroll in _enrollRepository.Where(t => t.TrialId == trialId /*&& t.TaskAllocationRule.IsEnable*/ && /*(t.EnrollStatus >= EnrollStatus.ConfirmIntoGroup ||*/ t.Trial.SubjectDoctorUserList.Any(v => v.DoctorUserId == t.DoctorUser.Id))
|
||||
join user in _userRoleRepository.AsQueryable() on enroll.DoctorId equals user.DoctorId
|
||||
select new TrialDoctorUserSelectView()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -51,12 +51,12 @@ namespace IRaCIS.Core.Application.Service
|
|||
{
|
||||
return await ExcelExportHelper.ExportTemplateAsync(new ExportTemplateServiceDto()
|
||||
{
|
||||
Data=inDto.Data,
|
||||
commonDocumentRepository= _commonDocumentRepository,
|
||||
TemplateCode=inDto.TemplateCode,
|
||||
ExportFileName=inDto.ExportFileName,
|
||||
hostEnvironment=_hostEnvironment,
|
||||
IsEnglish=_userInfo.IsEn_Us,
|
||||
Data = inDto.Data,
|
||||
commonDocumentRepository = _commonDocumentRepository,
|
||||
TemplateCode = inDto.TemplateCode,
|
||||
ExportFileName = inDto.ExportFileName,
|
||||
hostEnvironment = _hostEnvironment,
|
||||
IsEnglish = _userInfo.IsEn_Us,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ namespace IRaCIS.Core.Application.Service
|
|||
//为防止脏数据 这里也多判断一次
|
||||
var existCurrentVisitTaskList = _visitTaskRepository.Where(t => t.TrialId == trialId && t.SubjectId == subjectVisit.SubjectId
|
||||
&& t.TrialReadingCriterionId == trialReadingCriterionConfig.TrialReadingCriterionId && t.TaskState == TaskState.Effect
|
||||
&& t.SourceSubjectVisitId == subjectVisit.Id).ToList();
|
||||
&& t.SourceSubjectVisitId == subjectVisit.Id && t.IsAnalysisCreate == false).ToList();
|
||||
|
||||
if (trialReadingCriterionConfig.ReadingType == ReadingMethod.Double)
|
||||
{
|
||||
|
|
@ -682,7 +682,7 @@ namespace IRaCIS.Core.Application.Service
|
|||
|
||||
var existCurrentVisitTaskList = _visitTaskRepository.Where(t => t.TrialId == trialId && t.SubjectId == subjectVisit.SubjectId
|
||||
&& t.TrialReadingCriterionId == trialReadingCriterionId && t.TaskState == TaskState.Effect
|
||||
/* && t.TaskAllocationState == TaskAllocationState.NotAllocate && t.DoctorUserId == null*/ && t.SourceSubjectVisitId == subjectVisit.Id).ToList();
|
||||
/* && t.TaskAllocationState == TaskAllocationState.NotAllocate && t.DoctorUserId == null*/ && t.SourceSubjectVisitId == subjectVisit.Id && t.IsAnalysisCreate == false).ToList();
|
||||
|
||||
VisitTask? task1 = existCurrentVisitTaskList.FirstOrDefault(t => t.ArmEnum == Arm.DoubleReadingArm1);
|
||||
VisitTask? task2 = existCurrentVisitTaskList.FirstOrDefault(t => t.ArmEnum == Arm.DoubleReadingArm2);
|
||||
|
|
@ -757,24 +757,14 @@ namespace IRaCIS.Core.Application.Service
|
|||
|
||||
if (trialReadingCriterionConfig.TaskAllocateObjEnum == TaskAllocateObj.Subject)
|
||||
{
|
||||
var allocateSubjectArmList = _visitTaskRepository.Where(t => t.TrialReadingCriterionId == trialReadingCriterionConfig.TrialReadingCriterionId && t.SubjectId == subjectVisit.SubjectId && t.TrialId == trialId && t.DoctorUserId != null && t.ArmEnum != Arm.JudgeArm)
|
||||
.Select(t => new { t.DoctorUserId, t.ArmEnum }).Distinct().ToList();
|
||||
|
||||
//当前任务没有分配医生,初次分配 不处理 只生成任务,后续根据生成的任务 再进行分配
|
||||
if (allocateSubjectArmList.Count == 0)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//并且配置了医生
|
||||
if (assignConfigList.Count > 0 && trialReadingCriterionConfig.IsFollowVisitAutoAssign)
|
||||
{
|
||||
|
||||
#region 后续访视 未分配的进行再次分配,重置的或者失效的 需要重新生成新的任务 (PM 有序退回 或者PM 有序 申请重阅)
|
||||
|
||||
|
||||
if (trialReadingCriterionConfig.IsReadingTaskViewInOrder == ReadingOrder.InOrder)
|
||||
{
|
||||
#region 当前访视处理
|
||||
|
||||
//配置了医生
|
||||
if (assignConfigList.Count > 0)
|
||||
{
|
||||
//之前有回退到影像上传的访视 那么当前访视一致性核查通过的时候,当前访视生成但是不分配出去(排除失访的)
|
||||
|
||||
|
|
@ -813,6 +803,13 @@ namespace IRaCIS.Core.Application.Service
|
|||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 后续访视处理
|
||||
|
||||
|
||||
|
||||
|
||||
//后续最近的未一致性核查通过的访视任务
|
||||
var followBackVisitTask = await _visitTaskRepository.Where(t => t.TrialId == trialId && t.TrialReadingCriterionId == trialReadingCriterionConfig.TrialReadingCriterionId && t.SubjectId == subjectVisit.SubjectId && t.VisitTaskNum > subjectVisit.VisitNum && t.ReadingCategory == ReadingCategory.Visit && t.SourceSubjectVisit.CheckState != CheckStateEnum.CVPassed && t.SourceSubjectVisit.IsLostVisit == false).OrderBy(t => t.VisitTaskNum).FirstOrDefaultAsync();
|
||||
|
|
@ -867,13 +864,13 @@ namespace IRaCIS.Core.Application.Service
|
|||
|
||||
if (arm1 != null)
|
||||
{
|
||||
arm1.IsUrgent = visit.IsUrgent;
|
||||
arm1.TaskUrgentType = urgentType;
|
||||
arm1.IsCanEditUrgentState = isCanEdit;
|
||||
|
||||
//有可能仅仅只分配了一个Subject 未分配 那么
|
||||
if (assignConfigList.Any(t => t.ArmEnum == Arm.DoubleReadingArm1) && task1 != null)
|
||||
{
|
||||
arm1.IsUrgent = visit.IsUrgent;
|
||||
arm1.TaskUrgentType = urgentType;
|
||||
arm1.IsCanEditUrgentState = isCanEdit;
|
||||
arm1.TaskAllocationState = TaskAllocationState.Allocated;
|
||||
arm1.AllocateTime = DateTime.Now;
|
||||
arm1.DoctorUserId = task1.DoctorUserId;
|
||||
|
|
@ -885,9 +882,6 @@ namespace IRaCIS.Core.Application.Service
|
|||
{
|
||||
var latestTask = visitGroup.Where(t => t.ArmEnum == Arm.DoubleReadingArm1).OrderByDescending(t => t.CreateTime).First();
|
||||
|
||||
|
||||
|
||||
|
||||
var taskOne = await _visitTaskRepository.AddAsync(new VisitTask()
|
||||
{
|
||||
TrialId = trialId,
|
||||
|
|
@ -909,15 +903,21 @@ namespace IRaCIS.Core.Application.Service
|
|||
BlindTrialSiteCode = latestTask.BlindTrialSiteCode,
|
||||
IsAnalysisCreate = latestTask.IsAnalysisCreate,
|
||||
IsSelfAnalysis = latestTask.IsSelfAnalysis,
|
||||
TaskAllocationState = TaskAllocationState.Allocated,
|
||||
AllocateTime = DateTime.Now,
|
||||
DoctorUserId = task1!.DoctorUserId,
|
||||
SuggesteFinishedTime = GetSuggessFinishTime(true, UrgentType.NotUrget),
|
||||
|
||||
TrialReadingCriterionId = latestTask.TrialReadingCriterionId,
|
||||
IsNeedClinicalDataSign = latestTask.IsNeedClinicalDataSign,
|
||||
IsClinicalDataSign = latestTask.IsClinicalDataSign
|
||||
});
|
||||
|
||||
if (assignConfigList.Any(t => t.ArmEnum == Arm.DoubleReadingArm1) && task1 != null)
|
||||
{
|
||||
taskOne.TaskAllocationState = TaskAllocationState.Allocated;
|
||||
taskOne.AllocateTime = DateTime.Now;
|
||||
taskOne.DoctorUserId = task1!.DoctorUserId;
|
||||
taskOne.SuggesteFinishedTime = GetSuggessFinishTime(true, UrgentType.NotUrget);
|
||||
}
|
||||
|
||||
|
||||
currentMaxCodeInt = currentMaxCodeInt + 1;
|
||||
|
||||
_fusionCache.Set<int>(CacheKeys.TrialStudyMaxCode(trialId), currentMaxCodeInt, TimeSpan.FromMinutes(30));
|
||||
|
|
@ -934,13 +934,14 @@ namespace IRaCIS.Core.Application.Service
|
|||
{
|
||||
var arm2 = visitGroup.FirstOrDefault(t => t.TaskState == TaskState.Effect && t.TaskAllocationState == TaskAllocationState.NotAllocate && t.DoctorUserId == null && t.ArmEnum == Arm.DoubleReadingArm2);
|
||||
if (arm2 != null)
|
||||
{
|
||||
//有可能仅仅只分配了一个Subject
|
||||
if (assignConfigList.Any(t => t.ArmEnum == Arm.DoubleReadingArm2) && task2 != null)
|
||||
{
|
||||
arm2.IsUrgent = visit.IsUrgent;
|
||||
arm2.TaskUrgentType = urgentType;
|
||||
arm2.IsCanEditUrgentState = isCanEdit;
|
||||
|
||||
//有可能仅仅只分配了一个Subject
|
||||
if (assignConfigList.Any(t => t.ArmEnum == Arm.DoubleReadingArm2) && task2 != null)
|
||||
{
|
||||
arm2.TaskAllocationState = TaskAllocationState.Allocated;
|
||||
arm2.AllocateTime = DateTime.Now;
|
||||
arm2.DoctorUserId = task2.DoctorUserId;
|
||||
|
|
@ -975,17 +976,20 @@ namespace IRaCIS.Core.Application.Service
|
|||
BlindTrialSiteCode = latestTask.BlindTrialSiteCode,
|
||||
IsAnalysisCreate = latestTask.IsAnalysisCreate,
|
||||
IsSelfAnalysis = latestTask.IsSelfAnalysis,
|
||||
TaskAllocationState = TaskAllocationState.Allocated,
|
||||
|
||||
AllocateTime = DateTime.Now,
|
||||
DoctorUserId = task2!.DoctorUserId,
|
||||
SuggesteFinishedTime = GetSuggessFinishTime(true, UrgentType.NotUrget),
|
||||
|
||||
TrialReadingCriterionId = latestTask.TrialReadingCriterionId,
|
||||
IsNeedClinicalDataSign = latestTask.IsNeedClinicalDataSign,
|
||||
IsClinicalDataSign = latestTask.IsClinicalDataSign
|
||||
});
|
||||
|
||||
if (assignConfigList.Any(t => t.ArmEnum == Arm.DoubleReadingArm2) && task2 != null)
|
||||
{
|
||||
taskTwo.TaskAllocationState = TaskAllocationState.Allocated;
|
||||
taskTwo.AllocateTime = DateTime.Now;
|
||||
taskTwo.DoctorUserId = task2!.DoctorUserId;
|
||||
taskTwo.SuggesteFinishedTime = GetSuggessFinishTime(true, UrgentType.NotUrget);
|
||||
}
|
||||
|
||||
currentMaxCodeInt = currentMaxCodeInt + 1;
|
||||
|
||||
_fusionCache.Set<int>(CacheKeys.TrialStudyMaxCode(trialId), currentMaxCodeInt, TimeSpan.FromMinutes(30));
|
||||
|
|
@ -994,10 +998,15 @@ namespace IRaCIS.Core.Application.Service
|
|||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
}
|
||||
//无序的时候 生成任务并分配出去
|
||||
else
|
||||
{
|
||||
//无序的时候 生成任务并分配出去
|
||||
if (assignConfigList.Count > 0 && trialReadingCriterionConfig.IsFollowVisitAutoAssign)
|
||||
{
|
||||
var defaultState = trialReadingCriterionConfig.FollowVisitAutoAssignDefaultState == TaskAllocateDefaultState.InitAllocated ? TaskAllocationState.InitAllocated : TaskAllocationState.Allocated;
|
||||
|
||||
|
|
@ -1018,21 +1027,10 @@ namespace IRaCIS.Core.Application.Service
|
|||
task2.AllocateTime = DateTime.Now;
|
||||
task2.SuggesteFinishedTime = GetSuggessFinishTime(true, UrgentType.NotUrget);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
else
|
||||
//后续访视不自动分配,或者配置的医生数量不足,就不进行分配
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1086,7 +1084,7 @@ namespace IRaCIS.Core.Application.Service
|
|||
|
||||
|
||||
//该Subject 之前是否有已分配的 如果改变配置 可能会出现 一个Subject 分配的同一个医生 有的在Arm1 有的在Arm2
|
||||
var allocateSubjectArmList = _visitTaskRepository.Where(t => t.SubjectId == subjectVisit.SubjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.TrialId == trialId && t.DoctorUserId != null && t.ArmEnum != Arm.JudgeArm)
|
||||
var allocateSubjectArmList = _visitTaskRepository.Where(t => t.SubjectId == subjectVisit.SubjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.TrialId == trialId && t.DoctorUserId != null && t.ArmEnum != Arm.JudgeArm && t.IsAnalysisCreate == false)
|
||||
.Select(t => new { t.DoctorUserId, t.ArmEnum }).Distinct().ToList();
|
||||
|
||||
//不是初次分配
|
||||
|
|
@ -1107,7 +1105,7 @@ namespace IRaCIS.Core.Application.Service
|
|||
if (trialReadingCriterionConfig.IsReadingTaskViewInOrder == ReadingOrder.InOrder)
|
||||
{
|
||||
//之前有回退到影像上传的访视 那么当前访视一致性核查通过的时候,当前访视生成但是不分配出去(排除失访的)
|
||||
var beforeBackVisitTask = await _visitTaskRepository.Where(t => t.TrialId == trialId && t.SubjectId == subjectVisit.SubjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.VisitTaskNum < subjectVisit.VisitNum && t.ReadingCategory == ReadingCategory.Visit && t.SourceSubjectVisit.CheckState != CheckStateEnum.CVPassed && t.SourceSubjectVisit.IsLostVisit == false).OrderBy(t => t.VisitTaskNum).FirstOrDefaultAsync();
|
||||
var beforeBackVisitTask = await _visitTaskRepository.Where(t => t.TrialId == trialId && t.SubjectId == subjectVisit.SubjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.VisitTaskNum < subjectVisit.VisitNum && t.ReadingCategory == ReadingCategory.Visit && t.SourceSubjectVisit.CheckState != CheckStateEnum.CVPassed && t.SourceSubjectVisit.IsLostVisit == false && t.IsAnalysisCreate == false).OrderBy(t => t.VisitTaskNum).FirstOrDefaultAsync();
|
||||
|
||||
|
||||
if (beforeBackVisitTask == null)
|
||||
|
|
@ -1132,7 +1130,7 @@ namespace IRaCIS.Core.Application.Service
|
|||
#endregion
|
||||
|
||||
|
||||
var followBackVisitTask = await _visitTaskRepository.Where(t => t.TrialId == trialId && t.SubjectId == subjectVisit.SubjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.VisitTaskNum > subjectVisit.VisitNum && t.ReadingCategory == ReadingCategory.Visit && t.SourceSubjectVisit.CheckState != CheckStateEnum.CVPassed && t.SourceSubjectVisit.IsLostVisit == false).OrderBy(t => t.VisitTaskNum).FirstOrDefaultAsync();
|
||||
var followBackVisitTask = await _visitTaskRepository.Where(t => t.TrialId == trialId && t.SubjectId == subjectVisit.SubjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.VisitTaskNum > subjectVisit.VisitNum && t.ReadingCategory == ReadingCategory.Visit && t.SourceSubjectVisit.CheckState != CheckStateEnum.CVPassed && t.SourceSubjectVisit.IsLostVisit == false && t.IsAnalysisCreate == false).OrderBy(t => t.VisitTaskNum).FirstOrDefaultAsync();
|
||||
|
||||
//存在退回访视1 又退回基线 这种情况 生成任务 考虑基线先一致性核查通过,但是访视1还未通过时 生成任务
|
||||
var followVisitTaskList = await _visitTaskRepository
|
||||
|
|
@ -1411,7 +1409,9 @@ namespace IRaCIS.Core.Application.Service
|
|||
.Where(x => x.ClinicalDataTrialSet.UploadRole == UploadRole.PM || x.FileCount > 0)
|
||||
.Include(t => t.ReadingClinicalDataPDFList).Include(t => t.ClinicalDataTrialSet).ToList();
|
||||
|
||||
|
||||
//防止多标准重复插入
|
||||
if (!_readingConsistentClinicalDataRepository.Any(t => t.SubjectId == subjectId))
|
||||
{
|
||||
foreach (var clinicalData in clinicalDataList)
|
||||
{
|
||||
var consistnentClinicalData = _mapper.Map<ReadingConsistentClinicalData>(clinicalData);
|
||||
|
|
@ -1435,6 +1435,8 @@ namespace IRaCIS.Core.Application.Service
|
|||
|
||||
await _readingConsistentClinicalDataRepository.AddAsync(consistnentClinicalData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var task in generateTaskCommand.GenerataConsistentTaskList)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ using IRaCIS.Core.Infrastructure;
|
|||
using MassTransit;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using Subject = IRaCIS.Core.Domain.Models.Subject;
|
||||
|
||||
|
|
@ -45,7 +46,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
IRepository<SubjectCanceDoctor> _subjectCanceDoctorRepository,
|
||||
IRepository<ReadingTaskQuestionMark> _readingTaskQuestionMarkRepository,
|
||||
IRepository<ReadingTableAnswerRowInfo> _readingTableAnswerRowInfoRepository,
|
||||
IRepository<ReadingCustomTag> _readingCustomTagRepository,
|
||||
//IRepository<ReadingCustomTag> _readingCustomTagRepository,
|
||||
IRepository<TaskInfluence> _taskInfluenceRepository,
|
||||
IRepository<TrialQCQuestionAnswer> _trialQCQuestionAnswerRepository,
|
||||
ILogger<VisitTaskService> _logger,
|
||||
|
|
@ -74,7 +75,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
return await _visitTaskRepository.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<List<TrialReadingCriterionDto>> GetTrialCriterionList(Guid trialId, bool isHaveSigned = true, bool? isAutoCreate = null)
|
||||
public async Task<List<TrialReadingCriterionDto>> GetTrialCriterionList(Guid trialId, bool isHaveSigned = true, bool? isAutoCreate = null, bool? isRandom = null)
|
||||
{
|
||||
var list = await _readingQuestionCriterionTrialRepository.Where(t => t.TrialId == trialId && t.IsConfirm)
|
||||
|
||||
|
|
@ -107,7 +108,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
|
||||
|
||||
return list.AsQueryable().WhereIf(isHaveSigned == true, t => t.ReadingInfoSignTime != null)
|
||||
.WhereIf(isAutoCreate == false, t => t.IsAutoCreate == isAutoCreate).ToList();
|
||||
.WhereIf(isAutoCreate == false, t => t.IsAutoCreate == isAutoCreate)
|
||||
.WhereIf(isRandom == true, t => t.IsReadingTaskViewInOrder == ReadingOrder.Random).ToList();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -761,6 +763,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
.WhereIf(inQuery.EndAllocateDate != null, t => t.AllocateTime < inQuery.EndAllocateDate)
|
||||
.WhereIf(inQuery.BeginSignTime != null, t => t.SignTime > inQuery.BeginSignTime)
|
||||
.WhereIf(inQuery.EndSignTime != null, t => t.SignTime < inQuery.EndSignTime)
|
||||
.WhereIf(inQuery.RandomOrder != null, t => t.RandomOrder == inQuery.RandomOrder)
|
||||
.WhereIf(inQuery.IsRandomOrderList == true, t => (t.ReadingTaskState == ReadingTaskState.WaitReading || t.ReadingTaskState == ReadingTaskState.Reading) && t.TaskAllocationState == TaskAllocationState.Allocated)
|
||||
.ProjectTo<ReadingTaskView>(_mapper.ConfigurationProvider);
|
||||
|
||||
var defalutSortArray = new string[] { nameof(ReadingTaskView.IsUrgent) + " desc", nameof(ReadingTaskView.SubjectCode), nameof(ReadingTaskView.VisitTaskNum) };
|
||||
|
|
@ -961,7 +965,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
//随机阅片
|
||||
else
|
||||
{
|
||||
var taskQuery = _visitTaskRepository.Where(x => x.TrialId == inQuery.TrialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect && x.TrialReadingCriterionId == trialReadingCriterionId)
|
||||
var taskQuery = _visitTaskRepository.Where(x => x.TrialId == inQuery.TrialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect && x.TrialReadingCriterionId == trialReadingCriterionId && x.Subject.IsSubjectQuit == false)
|
||||
.Where(x => !x.Subject.IsDeleted).Where(x => (x.IsNeedClinicalDataSign && x.IsClinicalDataSign) || !x.IsNeedClinicalDataSign);
|
||||
|
||||
iRUnReadOut = new IRUnReadOutDto()
|
||||
|
|
@ -1005,22 +1009,26 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
|
||||
if (critrion.IsReadingTaskViewInOrder == ReadingOrder.InOrder)
|
||||
{
|
||||
var visitQuery = _visitTaskRepository.Where(x => x.TrialId == inQuery.TrialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect)
|
||||
var extralObj = _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.TrialExtraConfigJsonStr }).FirstOrDefault();
|
||||
|
||||
var extralConfig = JsonConvert.DeserializeObject<TrialExtraConfig>(extralObj?.TrialExtraConfigJsonStr) ?? new TrialExtraConfig();
|
||||
|
||||
var visitQuery = _visitTaskRepository.Where(x => x.TrialId == inQuery.TrialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect && x.Subject.IsSubjectQuit == false)
|
||||
|
||||
.WhereIf(inQuery.SubjectId != null, x => x.SubjectId == inQuery.SubjectId)
|
||||
//前序 不存在 未生成任务的访视
|
||||
.WhereIf(critrion.IsAutoCreate == false, t => !t.Subject.SubjectCriteriaEvaluationVisitFilterList.Where(t => t.TrialReadingCriterionId == trialReadingCriterionId).Any(f => f.IsGeneratedTask == false && t.VisitTaskNum > f.SubjectVisit.VisitNum))
|
||||
|
||||
// 前序 不存在 未一致性核查未通过的
|
||||
.Where(t => !t.Subject.SubjectVisitList.Any(sv => sv.CheckState != CheckStateEnum.CVPassed && t.VisitTaskNum >= sv.VisitNum))
|
||||
.Where(t => !t.Subject.SubjectVisitList.Where(t => extralConfig.IsOpenLostVistRead ? t.IsLostVisit == false : true).Any(sv => sv.CheckState != CheckStateEnum.CVPassed && t.VisitTaskNum >= sv.VisitNum))
|
||||
//.WhereIf(critrion.IsAutoCreate == false, t => t.Subject.SubjectCriteriaEvaluationVisitFilterList.Where(t => t.TrialReadingCriterionId == trialReadingCriterionId).Any(t => t.IsGeneratedTask == false) ?
|
||||
//t.VisitTaskNum <= t.Subject.SubjectCriteriaEvaluationVisitFilterList.Where(t => t.TrialReadingCriterionId == trialReadingCriterionId && t.IsGeneratedTask == false).Min(t => t.SubjectVisit.VisitNum) : true)
|
||||
//.Where(t => t.Subject.SubjectVisitList.Any(t => t.CheckState != CheckStateEnum.CVPassed) ? t.VisitTaskNum <= t.Subject.SubjectVisitList.Where(t => t.CheckState != CheckStateEnum.CVPassed).Min(t => t.VisitNum) : true)
|
||||
//满足前序访视不存在 需要签署但是未签署 sql 相当复杂 同时想查询所有未读的统计数字 就无法统计 byzhouhang
|
||||
//但是加字段 IsFrontTaskNeedSignButNotSign 那么签名临床数据的时候,要对该subject 该标准的有效的任务 这个字段需要在签名的时候维护 采取这种方式 统计数字灵活
|
||||
//.Where(t => t.Subject.SubjectVisitTaskList.AsQueryable().Where(visitTaskLambda).Any(c => c.IsNeedClinicalDataSign == true && c.IsClinicalDataSign == false && c.VisitTaskNum < t.VisitTaskNum))
|
||||
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => (t.Subject.Code.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate));
|
||||
|
||||
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => (t.Subject.Code.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate))
|
||||
;
|
||||
|
||||
var visitGroupQuery = visitQuery.GroupBy(x => new { x.SubjectId, x.Subject.Code, x.BlindSubjectCode });
|
||||
|
||||
|
|
@ -1035,14 +1043,15 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
UnReadTaskCount = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned).Count(),
|
||||
|
||||
//未读 里可读任务量
|
||||
UnReadCanReadTaskCount = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned && y.IsFrontTaskNeedSignButNotSign == false && (y.IsNeedClinicalDataSign == false || y.IsClinicalDataSign == true)
|
||||
UnReadCanReadTaskCount = x.Where(t => extralConfig.IsOpenLostVistRead ? true : t.IsFrontTaskNeedSignButNotSign == false).Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned && (y.IsNeedClinicalDataSign == false || y.IsClinicalDataSign == true)
|
||||
//不能对包含聚合或子查询的表达式执行聚合函数
|
||||
//&& !x.Any(t => t.ReadingTaskState != ReadingTaskState.HaveSigned && t.IsNeedClinicalDataSign == true && t.IsClinicalDataSign == false && t.VisitTaskNum<y.VisitTaskNum )
|
||||
).Count(),
|
||||
|
||||
|
||||
UnReadCanReadTaskList = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned)
|
||||
.Where(y => y.IsFrontTaskNeedSignButNotSign == false && (y.IsNeedClinicalDataSign == false || y.IsClinicalDataSign == true))
|
||||
.Where(t => extralConfig.IsOpenLostVistRead ? true : t.IsFrontTaskNeedSignButNotSign == false)
|
||||
.Where(y => /*y.IsFrontTaskNeedSignButNotSign == false &&*/ (y.IsNeedClinicalDataSign == false || y.IsClinicalDataSign == true))
|
||||
.OrderBy(x => x.VisitTaskNum)
|
||||
.Select(u => new IRUnreadTaskView()
|
||||
{
|
||||
|
|
@ -1055,6 +1064,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
ReadingCategory = u.ReadingCategory,
|
||||
IsAnalysisCreate = u.IsAnalysisCreate,
|
||||
ArmEnum = u.ArmEnum,
|
||||
IsExistUnprocessedFeedback = u.UserFeedBackList.Any(t => t.State == 0),
|
||||
TrialReadingCriterionId = u.TrialReadingCriterionId,
|
||||
IsNeedClinicalDataSign = u.IsNeedClinicalDataSign,
|
||||
IsClinicalDataSign = u.IsClinicalDataSign,
|
||||
|
|
@ -1085,6 +1095,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
ReadingCategory = u.ReadingCategory,
|
||||
IsAnalysisCreate = u.IsAnalysisCreate,
|
||||
ArmEnum = u.ArmEnum,
|
||||
IsExistUnprocessedFeedback = u.UserFeedBackList.Any(t => t.State == 0),
|
||||
TrialReadingCriterionId = u.TrialReadingCriterionId,
|
||||
IsNeedClinicalDataSign = u.IsNeedClinicalDataSign,
|
||||
IsClinicalDataSign = u.IsClinicalDataSign,
|
||||
|
|
@ -1160,7 +1171,9 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
|
||||
var visitQuery = _visitTaskRepository.Where(x => x.TrialId == trialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect)
|
||||
.WhereIf(inQuery.SubjectId != null, x => x.SubjectId == inQuery.SubjectId)
|
||||
.WhereIf(!string.IsNullOrEmpty(subjectCode), t => (t.Subject.Code.Contains(subjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(subjectCode!) && t.IsAnalysisCreate));
|
||||
.WhereIf(!string.IsNullOrEmpty(subjectCode), t => (t.Subject.Code.Contains(subjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(subjectCode!) && t.IsAnalysisCreate))
|
||||
.WhereIf(critrion.CriterionType == CriterionType.OCT, t => t.ReadingCategory == ReadingCategory.Visit ? t.SourceSubjectVisit.NoneDicomStudyList.SelectMany(t => t.ImageLabelNoneDicomFileList).Any() : true)
|
||||
.WhereIf(critrion.CriterionType == CriterionType.IVUS, t => t.ReadingCategory == ReadingCategory.Visit ? t.SourceSubjectVisit.TaskStudyList.Any() : true);
|
||||
|
||||
var visitGroupQuery = visitQuery.GroupBy(x => new { x.SubjectId, x.Subject.Code, x.BlindSubjectCode });
|
||||
|
||||
|
|
@ -1194,6 +1207,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
ReadingCategory = u.ReadingCategory,
|
||||
IsAnalysisCreate = u.IsAnalysisCreate,
|
||||
ArmEnum = u.ArmEnum,
|
||||
IsExistUnprocessedFeedback = u.UserFeedBackList.Any(t => t.State == 0),
|
||||
TrialReadingCriterionId = u.TrialReadingCriterionId,
|
||||
IsNeedClinicalDataSign = u.IsNeedClinicalDataSign,
|
||||
IsClinicalDataSign = u.IsClinicalDataSign,
|
||||
|
|
@ -1392,6 +1406,12 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
|
||||
task.ReReadingApplyState = ReReadingApplyState.TrialGroupHaveApplyed;
|
||||
|
||||
if (!isSPMjoin)
|
||||
{
|
||||
task.ReReadingApplyState = ReReadingApplyState.Agree;
|
||||
task.PMBackReason = applyReReadingCommand.RequestReReadingReason;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1603,13 +1623,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
var origenalTask = (await _visitTaskRepository.Where(t => item.OriginalReReadingTaskId == t.Id).FirstOrDefaultAsync()).IfNullThrowException();
|
||||
|
||||
|
||||
if ((origenalTask.TaskState != TaskState.Effect && origenalTask.TaskState != TaskState.Freeze))
|
||||
{
|
||||
await _visitTaskReReadingRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, u => new VisitTaskReReading() { RequestReReadingConfirmUserId = _userInfo.UserRoleId, RequestReReadingResultEnum = RequestReReadingResult.Invalid });
|
||||
|
||||
//---当前申请重阅任务的状态,已被其他任务重阅已影响,不允许对该状态下的任务进行重阅同意与否操作
|
||||
throw new BusinessValidationFailedException(_localizer["VisitTask_ReapplyStatusConflict"]);
|
||||
}
|
||||
|
||||
//更新申请信息
|
||||
var visitTaskReReadingAppply = await _visitTaskReReadingRepository.FirstOrDefaultAsync(t => t.Id == item.Id);
|
||||
|
|
@ -1622,6 +1636,13 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
|
||||
if (agreeReReadingCommand.RequestReReadingResultEnum == RequestReReadingResult.Agree)
|
||||
{
|
||||
if ((origenalTask.TaskState != TaskState.Effect && origenalTask.TaskState != TaskState.Freeze))
|
||||
{
|
||||
await _visitTaskReReadingRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, u => new VisitTaskReReading() { RequestReReadingConfirmUserId = _userInfo.UserRoleId, RequestReReadingResultEnum = RequestReReadingResult.Invalid });
|
||||
|
||||
//---当前申请重阅任务的状态,已被其他任务重阅已影响,不允许对该状态下的任务进行重阅同意与否操作
|
||||
throw new BusinessValidationFailedException(_localizer["VisitTask_ReapplyStatusConflict"]);
|
||||
}
|
||||
|
||||
await AgreeReReading(origenalTask.Id, visitTaskReReadingAppply.RequestReReadingType, visitTaskReReadingAppply);
|
||||
|
||||
|
|
@ -1698,12 +1719,12 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
if (criterionConfig.CriterionType == CriterionType.RECIST1Point1 && criterionConfig.IsAdditionalAssessment)
|
||||
{
|
||||
// PM申请 SPM / CPM审批
|
||||
if (requestReReadingType == RequestReReadingType.TrialGroupApply)
|
||||
{
|
||||
filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == origenalTask.TrialReadingCriterionId || t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB);
|
||||
}
|
||||
//if (requestReReadingType == RequestReReadingType.TrialGroupApply)
|
||||
//{
|
||||
// filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == origenalTask.TrialReadingCriterionId || t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB);
|
||||
//}
|
||||
//IR 申请 PM审批
|
||||
else
|
||||
if (requestReReadingType != RequestReReadingType.TrialGroupApply)
|
||||
{
|
||||
|
||||
// 1.1 基线任务影响BM任务
|
||||
|
|
@ -1721,10 +1742,13 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requestReReadingType != RequestReReadingType.TrialGroupApply)
|
||||
{
|
||||
//默认影响的都是该标准的任务
|
||||
filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == origenalTask.TrialReadingCriterionId);
|
||||
}
|
||||
}
|
||||
|
||||
//PM申请 SPM / CPM审批 回退访视,因此这里不生成访视任务 影响多个标准的任务
|
||||
if (requestReReadingType == RequestReReadingType.TrialGroupApply)
|
||||
|
|
@ -2192,17 +2216,18 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
|
||||
private void CopyForms(VisitTask newTask, VisitTask origenalTask)
|
||||
{
|
||||
|
||||
newTask.IsCopyLesionAnswer = true;
|
||||
//自定义
|
||||
var readingCustomTagList = _readingCustomTagRepository.Where(t => t.VisitTaskId == origenalTask.Id).ToList();
|
||||
//var readingCustomTagList = _readingCustomTagRepository.Where(t => t.VisitTaskId == origenalTask.Id).ToList();
|
||||
|
||||
foreach (var item in readingCustomTagList)
|
||||
{
|
||||
item.Id = Guid.Empty;
|
||||
item.VisitTaskId = newTask.Id;
|
||||
}
|
||||
//foreach (var item in readingCustomTagList)
|
||||
//{
|
||||
// item.Id = Guid.Empty;
|
||||
// item.VisitTaskId = newTask.Id;
|
||||
// item.MeasureData = item.MeasureData.Replace(origenalTask.Id.ToString(), newTask.Id.ToString());
|
||||
//}
|
||||
|
||||
_ = _readingCustomTagRepository.AddRangeAsync(readingCustomTagList).Result;
|
||||
//_ = _readingCustomTagRepository.AddRangeAsync(readingCustomTagList).Result;
|
||||
|
||||
|
||||
|
||||
|
|
@ -2213,6 +2238,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
{
|
||||
item.Id = Guid.Empty;
|
||||
item.VisitTaskId = newTask.Id;
|
||||
|
||||
}
|
||||
_ = _readingTaskQuestionAnswerRepository.AddRangeAsync(readingTaskQuestionAnswerList).Result;
|
||||
|
||||
|
|
@ -2224,6 +2250,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
item.Id = Guid.Empty;
|
||||
item.FristAddTaskId = item.VisitTaskId == item.FristAddTaskId ? newTask.Id : item.FristAddTaskId;
|
||||
item.VisitTaskId = newTask.Id;
|
||||
|
||||
item.MeasureData = item.MeasureData.Replace(origenalTask.Id.ToString(), newTask.Id.ToString());
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2237,7 +2265,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
var originalVisitTaskId = item.VisitTaskId;
|
||||
var originalFristAddTaskId = item.FristAddTaskId;
|
||||
|
||||
var newRowId= NewId.NextSequentialGuid();
|
||||
var newRowId = NewId.NextSequentialGuid();
|
||||
|
||||
foreach (var mark in readingTaskQuestionMarkList)
|
||||
{
|
||||
|
|
@ -2250,6 +2278,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
//默认值是当前任务添加的
|
||||
item.FristAddTaskId = originalVisitTaskId == originalFristAddTaskId ? newTask.Id : item.FristAddTaskId;
|
||||
|
||||
item.MeasureData = item.MeasureData.Replace(origenalTask.Id.ToString(), newTask.Id.ToString());
|
||||
|
||||
foreach (var item2 in item.LesionAnswerList)
|
||||
{
|
||||
|
|
@ -2273,11 +2302,11 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
/// PM 设置任务 退回
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPut("{trialId:guid}/{taskId:guid}")]
|
||||
[HttpPut("{trialId:guid}/{taskId:guid}/{pmBackReason}")]
|
||||
[UnitOfWork]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
|
||||
public async Task<IResponseOutput> PMSetTaskBack(Guid trialId, Guid taskId)
|
||||
public async Task<IResponseOutput> PMSetTaskBack(Guid trialId, Guid taskId, string pmBackReason)
|
||||
{
|
||||
|
||||
//var trialConfig = (await _trialRepository.Where(t => t.Id == trialId).Select(t => new { TrialId = t.Id, t.IsReadingTaskViewInOrder, t.ReadingType }).FirstOrDefaultAsync()).IfNullThrowException();
|
||||
|
|
@ -2338,7 +2367,6 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
|
||||
#region 有序 无序公用流程
|
||||
|
||||
|
||||
//执行类似一致性核查回退流程 回退访视到影像上传流程
|
||||
await VisitBackAsync(task.SourceSubjectVisitId);
|
||||
|
||||
|
|
@ -2455,6 +2483,9 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
|
||||
}
|
||||
|
||||
var trackTask = await _visitTaskRepository.FindAsync(task.Id);
|
||||
trackTask.PMBackReason = pmBackReason;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -2567,7 +2598,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
sv.CurrentActionUserId = null;
|
||||
sv.PreliminaryAuditUserId = null;
|
||||
sv.ReviewAuditUserId = null;
|
||||
|
||||
sv.SecondReviewState = SecondReviewState.None;
|
||||
|
||||
if (sv.IsBaseLine)
|
||||
{
|
||||
|
|
@ -2606,7 +2637,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
|
||||
private bool IsSpmOrCPM()
|
||||
{
|
||||
return _userInfo.UserTypeEnumInt == (int)UserTypeEnum.SPM || _userInfo.UserTypeEnumInt == (int)UserTypeEnum.CPM ;
|
||||
return _userInfo.UserTypeEnumInt == (int)UserTypeEnum.SPM || _userInfo.UserTypeEnumInt == (int)UserTypeEnum.CPM;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -2617,7 +2648,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
/// <param name="applyId"> 申请记录的Id</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{taskId:guid}/{isReReading:bool}")]
|
||||
public async Task<(List<InfluenceTaskInfo>, object)> GetReReadingOrBackInfluenceTaskList(Guid taskId, bool isReReading, Guid? applyId)
|
||||
public async Task<IResponseOutput<List<InfluenceTaskInfo>>> GetReReadingOrBackInfluenceTaskList(Guid taskId, bool isReReading, Guid? applyId)
|
||||
{
|
||||
var isIRAppyTaskInfluenced = false;
|
||||
|
||||
|
|
@ -2635,13 +2666,16 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
//IR 申请1.1 基线重阅,影响附加评估所有的任务
|
||||
var isIR1Point1AdditionalAssessmentBaseline = false;
|
||||
|
||||
//是IR 申请 PM 审批流程
|
||||
var isIRApplyPMApprovalProcess = (IsPMOrAPm() && applyId != null && await _visitTaskReReadingRepository.AnyAsync(t => t.Id == applyId && t.CreateUserRole.UserTypeEnum == UserTypeEnum.IndependentReviewer))
|
||||
|| (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.IndependentReviewer && applyId == null);
|
||||
|
||||
//附加评估 IR 和PM 看到的影响列表不一样
|
||||
if (criterionConfig.CriterionType == CriterionType.RECIST1Point1 && criterionConfig.IsAdditionalAssessment)
|
||||
{
|
||||
|
||||
// IR 申请 PM 同意
|
||||
if (((IsPMOrAPm() && applyId != null && await _visitTaskReReadingRepository.AnyAsync(t => t.Id == applyId && t.CreateUserRole.UserTypeEnum == UserTypeEnum.IndependentReviewer))
|
||||
|| (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.IndependentReviewer && applyId == null)))
|
||||
if (isIRApplyPMApprovalProcess)
|
||||
{
|
||||
|
||||
// 1.1 基线任务影响BM任务
|
||||
|
|
@ -2660,18 +2694,21 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
}
|
||||
|
||||
}
|
||||
//(1、PM回退,PM申请重阅,SPM同意回退)
|
||||
//(1、PM回退,PM申请重阅,SPM同意回退)--20250804-感觉这里没用,不用限制
|
||||
//else
|
||||
//{
|
||||
// filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == filterObj.TrialReadingCriterionId || t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB);
|
||||
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == filterObj.TrialReadingCriterionId || t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB);
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
if (isReReading == true && isIRApplyPMApprovalProcess)
|
||||
{
|
||||
//默认影响的都是该标准的任务
|
||||
filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == filterObj.TrialReadingCriterionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -2911,8 +2948,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
}
|
||||
#endregion
|
||||
|
||||
|
||||
return (list, new { IsIRAppyTaskInfluenced = isIRAppyTaskInfluenced });
|
||||
//IsIRAppyTaskInfluenced 列表中存在IR已申请重阅的任务!
|
||||
return ResponseOutput.Ok(list, new { IsIRAppyTaskInfluenced = isIRAppyTaskInfluenced });
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -2931,6 +2968,88 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
|||
}
|
||||
|
||||
|
||||
#region 完全随机设置序号
|
||||
[HttpPost]
|
||||
[UnitOfWork]
|
||||
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
||||
public async Task<IResponseOutput> SetRandomTaskOrder(SetRandomTaskOrderCommand inCommand)
|
||||
{
|
||||
|
||||
if (inCommand.IsAutoSet)
|
||||
{
|
||||
//找到所有的已分配的,未阅片的,生效的 非一致性分析
|
||||
|
||||
var needRandomOrderList = _visitTaskRepository.Where(t => t.TrialId == inCommand.TrialId && t.TrialReadingCriterionId == inCommand.TrialReadingCriterionId && t.DoctorUserId == inCommand.DoctorUserId)
|
||||
.Where(t => t.TaskAllocationState == TaskAllocationState.Allocated && t.ReadingTaskState == ReadingTaskState.WaitReading && (t.TaskState == TaskState.Effect || t.TaskState == TaskState.Freeze))
|
||||
.Select(t => t.Id).ToList();
|
||||
|
||||
|
||||
//var haveSignOrderList = _visitTaskRepository.Where(t => t.TrialId == inCommand.TrialId && t.TrialReadingCriterionId == inCommand.TrialReadingCriterionId && t.DoctorUserId == inCommand.DoctorUserId)
|
||||
// .Where(t => t.ReadingTaskState == ReadingTaskState.HaveSigned && t.SignTime != null && (t.TaskState == TaskState.Effect || t.TaskState == TaskState.Freeze))
|
||||
// .Select(t => t.RandomOrder).ToList();
|
||||
|
||||
//已阅,阅片中任务的序号
|
||||
await _visitTaskRepository.BatchUpdateNoTrackingAsync(t => t.TrialId == inCommand.TrialId && t.TrialReadingCriterionId == inCommand.TrialReadingCriterionId
|
||||
&& t.DoctorUserId == inCommand.DoctorUserId && t.ReadingTaskState != ReadingTaskState.WaitReading, u => new VisitTask() { RandomOrder = null });
|
||||
|
||||
//随机赋值编号 比如要处理5个任务,实例化一个包含1-5的数组,每次随机取出一个
|
||||
List<int> availableNumbers = Enumerable.Range(1, needRandomOrderList.Count).ToList();
|
||||
Random rng = new Random();
|
||||
foreach (var id in needRandomOrderList)
|
||||
{
|
||||
int randomIndex = rng.Next(availableNumbers.Count);
|
||||
|
||||
var num = availableNumbers[randomIndex];
|
||||
|
||||
var order = 11 + 5 * (num - 1);
|
||||
|
||||
await _visitTaskRepository.BatchUpdateNoTrackingAsync(t => t.Id == id, t => new VisitTask() { RandomOrder = order });
|
||||
|
||||
availableNumbers.RemoveAt(randomIndex);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in inCommand.SetList)
|
||||
{
|
||||
var task = await _visitTaskRepository.Where(t => t.Id == item.Id).Select(t => new { t.RandomOrder, t.ReadingTaskState, t.TaskAllocationState, t.DoctorUserId }).FirstNotNullAsync();
|
||||
|
||||
if (task.ReadingTaskState != ReadingTaskState.WaitReading || task.DoctorUserId != inCommand.DoctorUserId
|
||||
|| task.TaskAllocationState != TaskAllocationState.Allocated)
|
||||
{
|
||||
//"任务不符合设置阅片序号条件"
|
||||
return ResponseOutput.NotOk(_localizer["VisitTask_NotRandomOrderTask"]);
|
||||
}
|
||||
|
||||
//设置交换序号的任务,可能不符合条件
|
||||
|
||||
//没有序号,直接设置
|
||||
if (task.RandomOrder == null)
|
||||
{
|
||||
await _visitTaskRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, t => new VisitTask() { RandomOrder = item.RandomOrder });
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_visitTaskRepository.Any(t => t.TrialId == inCommand.TrialId && t.TrialReadingCriterionId == inCommand.TrialReadingCriterionId && t.DoctorUserId == inCommand.DoctorUserId
|
||||
&& t.TaskAllocationState == TaskAllocationState.Allocated && t.ReadingTaskState == ReadingTaskState.WaitReading && (t.TaskState == TaskState.Effect || t.TaskState == TaskState.Freeze)
|
||||
&& t.RandomOrder == item.RandomOrder))
|
||||
{
|
||||
await _visitTaskRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, t => new VisitTask() { RandomOrder = item.RandomOrder });
|
||||
}
|
||||
else
|
||||
{
|
||||
//"序号已被占用!"
|
||||
return ResponseOutput.NotOk(_localizer["VisitTask_RandomOrderUserd"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseOutput.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region 暂时废弃
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Core.Application.ViewModel;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore.Migrations;
|
||||
|
||||
namespace IRaCIS.Core.Application.Service
|
||||
{
|
||||
|
|
@ -117,7 +118,9 @@ namespace IRaCIS.Core.Application.Service
|
|||
.ForMember(o => o.IsReadingShowPreviousResults, t => t.MapFrom(u => u.TrialReadingCriterion.IsReadingShowPreviousResults))
|
||||
.ForMember(o => o.DigitPlaces, t => t.MapFrom(u => u.TrialReadingCriterion.DigitPlaces))
|
||||
.ForMember(o => o.IseCRFShowInDicomReading, t => t.MapFrom(u => u.TrialReadingCriterion.IseCRFShowInDicomReading))
|
||||
.ForMember(o => o.CriterionType, t => t.MapFrom(u => u.TrialReadingCriterion.CriterionType));
|
||||
.ForMember(o => o.CriterionType, t => t.MapFrom(u => u.TrialReadingCriterion.CriterionType))
|
||||
.ForMember(o => o.IsSubjectQuit, t => t.MapFrom(u => u.Subject.IsSubjectQuit))
|
||||
;
|
||||
|
||||
CreateMap<VisitTask, VisitTaskView>().IncludeBase<VisitTask, VisitTaskViewBasic>()
|
||||
|
||||
|
|
@ -160,8 +163,10 @@ namespace IRaCIS.Core.Application.Service
|
|||
|
||||
|
||||
CreateMap<VisitTaskReReading, ReReadingTaskView>()
|
||||
.ForMember(o => o.ApplicantName, t => t.MapFrom(u => u.CreateUserRole.IdentityUser.FullName))
|
||||
.ForMember(o => o.ReReadingNewTaskCode, t => t.MapFrom(u => u.NewReReadingTask.TaskCode))
|
||||
.ForMember(o => o.OriginalReReadingTask, t => t.MapFrom(u => u.OriginalReReadingTask))
|
||||
//.ForMember(o => o.IsSubjectQuit, t => t.MapFrom(u => u.OriginalReReadingTask.Subject.IsSubjectQuit))
|
||||
;
|
||||
|
||||
|
||||
|
|
@ -221,6 +226,7 @@ namespace IRaCIS.Core.Application.Service
|
|||
.ForMember(o => o.IseCRFShowInDicomReading, t => t.MapFrom(u => u.VisitTask.TrialReadingCriterion.IseCRFShowInDicomReading))
|
||||
.ForMember(o => o.CriterionType, t => t.MapFrom(u => u.VisitTask.TrialReadingCriterion.CriterionType))
|
||||
|
||||
.ForMember(o => o.IsSubjectQuit, t => t.MapFrom(u => u.VisitTask.Subject.IsSubjectQuit))
|
||||
.ForMember(o => o.Id, t => t.MapFrom(u => u.Id))
|
||||
.ForMember(o => o.MedicalNo, t => t.MapFrom(u => u.VisitTask.Subject.MedicalNo))
|
||||
.ForMember(o => o.DoctorUser, t => t.MapFrom(u => u.VisitTask.DoctorUser))
|
||||
|
|
|
|||
|
|
@ -36,6 +36,14 @@ namespace IRaCIS.Core.Application.Service
|
|||
return await commonDocumentQueryable.ToPagedListAsync(queryCommonDocument);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<CommonDocument> GetCommonDocument(string code)
|
||||
{
|
||||
var find = await _commonDocumentRepository.Where(t => t.Code == code).FirstOrDefaultAsync();
|
||||
|
||||
return find;
|
||||
}
|
||||
|
||||
|
||||
public async Task<IResponseOutput> AddOrUpdateCommonDocument(CommonDocumentAddOrEdit addOrEditCommonDocument)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ namespace IRaCIS.Application.Contracts
|
|||
public string ValueCN { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class ChildInQuery:SortInput
|
||||
{
|
||||
public Guid ParentId { get; set; }
|
||||
}
|
||||
|
||||
public class AddOrEditBasicDic
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
|
||||
//--------------------------------------------------------------------
|
||||
// 此代码由liquid模板自动生成 byzhouhang 20240909
|
||||
// 生成时间 2025-10-28 06:22:47Z
|
||||
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
|
||||
//--------------------------------------------------------------------
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
namespace IRaCIS.Core.Application.ViewModel;
|
||||
|
||||
|
||||
public class GetReSendEmailInDto : PageInput
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
}
|
||||
public class EmailLogView : EmailLogAddOrEdit
|
||||
{
|
||||
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
public DateTime UpdateTime { get; set; }
|
||||
|
||||
public List<EmailRecipientLogView> RecipientList { get; set; }
|
||||
|
||||
public List<EmaliAttachmentInfo> AttachmentList { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class EmaliAttachmentInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 附件名称
|
||||
/// </summary>
|
||||
public string AttachmentName { get; set; }
|
||||
/// <summary>
|
||||
/// 附件路径
|
||||
/// </summary>
|
||||
public string AttachmentPath { get; set; }
|
||||
}
|
||||
|
||||
public class EmailRecipientLogView
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 邮件Id
|
||||
/// </summary>
|
||||
public Guid EmailLogId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 收件人姓名
|
||||
/// </summary>
|
||||
public string RecipientName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 收件人地址
|
||||
/// </summary>
|
||||
public string RecipientAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 收件人类型
|
||||
/// </summary>
|
||||
public RecipientType RecipientTypeEnum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class EmailLogAddOrEdit
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 邮件Id
|
||||
/// </summary>
|
||||
public string MessageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 唯一Id
|
||||
/// </summary>
|
||||
public string UniqueId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 邮件主题
|
||||
/// </summary>
|
||||
public string EmailSubject { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 日期
|
||||
/// </summary>
|
||||
public DateTime? EmailDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public string ErrorInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发件人地址
|
||||
/// </summary>
|
||||
public string SenderAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发件人姓名
|
||||
/// </summary>
|
||||
public string SenderName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 邮件内容
|
||||
/// </summary>
|
||||
public string Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 邮件状态
|
||||
/// </summary>
|
||||
public EmailState EmailStateEnum { get; set; }
|
||||
}
|
||||
|
||||
public class GetEmailInfoOutDto:EmailLogView
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class GetEmailInfoInDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid? TrialId { get; set; }
|
||||
}
|
||||
|
||||
public class SynchronizationEmailInDto
|
||||
{
|
||||
public Guid? TrialId { get; set; }
|
||||
}
|
||||
|
||||
public class ResendEmailInDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid? TrialId { get; set; }
|
||||
}
|
||||
|
||||
public class EmailAuthorization
|
||||
{
|
||||
public string FromEmail { get; set; } = string.Empty;
|
||||
|
||||
public string AuthorizationCode { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class EmailLogQuery:PageInput
|
||||
{
|
||||
|
||||
public Guid? TrialId { get; set; }
|
||||
|
||||
public DateTime? EmailStartDate { get; set; }
|
||||
|
||||
public DateTime? EmailEndDate { get; set; }
|
||||
|
||||
public EmailState? EmailStateEnum { get; set; }
|
||||
|
||||
public string ToRecipientName { get; set; } = string.Empty;
|
||||
|
||||
public string CcRecipientName { get; set; } = string.Empty;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -96,9 +96,6 @@ namespace IRaCIS.Core.Application.Contracts
|
|||
public bool IsDeleted { get; set; }
|
||||
|
||||
|
||||
|
||||
public CriterionType? CriterionTypeEnum { get; set; }
|
||||
|
||||
/// <summary> 业务模块 /// </summary>
|
||||
public int BusinessModuleEnum { get; set; }
|
||||
|
||||
|
|
@ -140,6 +137,24 @@ namespace IRaCIS.Core.Application.Contracts
|
|||
|
||||
public int? EmailDelaySeconds { get; set; }
|
||||
|
||||
|
||||
|
||||
[Comment("邮件配置的多个标准")]
|
||||
public List<CriterionType>? CriterionTypeList { get; set; }
|
||||
|
||||
//public CriterionType? CriterionTypeEnum { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class BatchUpdateEmailTopicCommand
|
||||
{
|
||||
[NotDefault]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string EmailTopic { get; set; } = string.Empty;
|
||||
|
||||
public string EmailTopicCN { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
// 生成时间 2022-03-28 16:43:52
|
||||
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
|
||||
//--------------------------------------------------------------------
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using IRaCIS.Core.Infra.EFCore.Common;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
|
|
@ -63,6 +64,26 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
|
||||
}
|
||||
|
||||
|
||||
public class ModuleTypeData
|
||||
{
|
||||
|
||||
public bool IsShow { get; set; }
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public Guid? ParentId { get; set; }
|
||||
public Guid DictionaryId { get; set; }
|
||||
|
||||
public int ShowOrder { get; set; }
|
||||
public string DictionaryValue { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class GetModuleTypeListInDto
|
||||
{
|
||||
public Guid TrialId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复制其他对象到当前对象
|
||||
/// </summary>
|
||||
|
|
@ -138,6 +159,12 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
public class FrontAuditConfigAddOrEdit
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 适用的标准
|
||||
/// </summary>
|
||||
public List<CriterionType> ApplyCriterionList { get; set; } = new List<CriterionType>() { };
|
||||
|
||||
public string Value { get; set; } = string.Empty;
|
||||
public string ValueCN { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
|
@ -148,6 +175,11 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
public Guid UpdateUserId { get; set; }
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 是否默认选择
|
||||
/// </summary>
|
||||
public bool IsDefaultChoice { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 字段的英文值
|
||||
/// </summary>
|
||||
|
|
@ -303,7 +335,7 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
public bool IsConfig { get; set; }
|
||||
public string DictionaryKey { get; set; } = string.Empty;
|
||||
|
||||
|
||||
public bool IsBeforeModifyView { get; set; } = true;
|
||||
|
||||
|
||||
//byzhouahng
|
||||
|
|
|
|||
|
|
@ -96,6 +96,8 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
|
||||
public int State { get; set; }
|
||||
|
||||
public Guid? PublishLogId { get; set; }
|
||||
|
||||
public List<BatchAddInternationalizationDto> AddList { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +114,8 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
|
||||
public int? State { get; set; }
|
||||
|
||||
public int InternationalizationType { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class BatchAddInternationalizationDto : BatchInternationalizationDto
|
||||
|
|
@ -121,6 +125,9 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
|
||||
public class InternationalizationSimpleDto : BatchInternationalizationDto
|
||||
{
|
||||
public string Version { get; set; }
|
||||
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.Service.Common.DTO
|
||||
{
|
||||
//public class MailModel
|
||||
//{
|
||||
//}
|
||||
}
|
||||
|
|
@ -182,13 +182,12 @@ namespace IRaCIS.Core.Application.Service
|
|||
/// <summary>
|
||||
/// 获取子项数组
|
||||
/// </summary>
|
||||
/// <param name="parentId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{parentId:guid}")]
|
||||
public async Task<List<BasicDicView>> GetChildList(Guid parentId)
|
||||
[HttpPost]
|
||||
public async Task<List<BasicDicView>> GetChildList(ChildInQuery inQuery)
|
||||
{
|
||||
return await _dicRepository.Where(t => t.ParentId == parentId)
|
||||
.OrderBy(t => t.ShowOrder).ProjectTo<BasicDicView>(_mapper.ConfigurationProvider).ToListAsync();
|
||||
return await _dicRepository.Where(t => t.ParentId == inQuery.ParentId)
|
||||
.ProjectTo<BasicDicView>(_mapper.ConfigurationProvider).SortToListAsync(inQuery);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,731 @@
|
|||
|
||||
//--------------------------------------------------------------------
|
||||
// 此代码由liquid模板自动生成 byzhouhang 20240909
|
||||
// 生成时间 2025-10-28 06:22:42Z
|
||||
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
|
||||
//--------------------------------------------------------------------
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.Interfaces;
|
||||
using IRaCIS.Core.Application.ViewModel;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using IdentityModel.Client;
|
||||
using MailKit;
|
||||
using MailKit.Net.Imap;
|
||||
using MailKit.Search;
|
||||
using MailKit.Security;
|
||||
using MassTransit;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Mail;
|
||||
using System.Threading.Tasks;
|
||||
using Panda.DynamicWebApi.Attributes;
|
||||
namespace IRaCIS.Core.Application.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 邮件日志
|
||||
/// </summary>
|
||||
/// <param name="_emailLogRepository"></param>
|
||||
/// <param name="systemEmailConfig"></param>
|
||||
/// <param name="_mapper"></param>
|
||||
/// <param name="_userInfo"></param>
|
||||
/// <param name="_localizer"></param>
|
||||
[ApiExplorerSettings(GroupName = "Common")]
|
||||
public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
|
||||
IRepository<Trial> _trialRepository,
|
||||
IRepository<EmailAttachmentLog> _emailAttachmentLogRepository,
|
||||
IOSSService oSSService,
|
||||
IRepository<EmailReSendLog> _emailReSendLog,
|
||||
|
||||
IRepository<EmailRecipientLog> _emailRecipientLogRepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
|
||||
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer): BaseService, IEmailLogService
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
||||
/// <summary>
|
||||
/// 获取邮件日志列表
|
||||
/// </summary>
|
||||
/// <param name="inQuery"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<PageOutput<EmailLogView>> GetEmailLogList(EmailLogQuery inDto)
|
||||
{
|
||||
var emailFromEmail = await _trialRepository.Where(x=>x.Id==inDto.TrialId).Select(x=>x.EmailFromEmail).FirstOrDefaultAsync();
|
||||
|
||||
if (emailFromEmail.IsNullOrEmpty())
|
||||
{
|
||||
emailFromEmail = _systemEmailConfig.FromEmail;
|
||||
}
|
||||
var emailLogQueryable = _emailLogRepository
|
||||
.Where(x=>x.SenderAddress== emailFromEmail)
|
||||
.WhereIf(inDto.EmailStartDate.HasValue, x => x.EmailDate >= inDto.EmailStartDate.Value)
|
||||
.WhereIf(inDto.EmailEndDate.HasValue, x => x.EmailDate <= inDto.EmailEndDate.Value)
|
||||
.WhereIf(inDto.EmailStateEnum.HasValue, x => x.EmailStateEnum == inDto.EmailStateEnum.Value)
|
||||
.WhereIf(inDto.CcRecipientName.IsNotNullOrEmpty(),x=>x.EmailRecipientLogList.Any(x=>x.RecipientTypeEnum==RecipientType.Cc&&x.RecipientName.Contains(inDto.CcRecipientName)))
|
||||
.WhereIf(inDto.ToRecipientName.IsNotNullOrEmpty(), x => x.EmailRecipientLogList.Any(x => x.RecipientTypeEnum == RecipientType.To && x.RecipientName.Contains(inDto.ToRecipientName)))
|
||||
.ProjectTo<EmailLogView>(_mapper.ConfigurationProvider);
|
||||
|
||||
var defalutSortArray = new string[] { nameof(EmailLogView.EmailDate) + " desc" };
|
||||
var pageList = await emailLogQueryable.ToPagedListAsync(inDto, defalutSortArray);
|
||||
|
||||
return pageList;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取重发信息
|
||||
/// </summary>
|
||||
/// <param name="inDto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<PageOutput<EmailLogView>> GetReSendEmail(GetReSendEmailInDto inDto)
|
||||
{
|
||||
var messageId = await _emailLogRepository.Where(x => x.Id == inDto.Id).Select(x => x.MessageId).FirstOrDefaultAsync();
|
||||
|
||||
var reSendMessagelist = await _emailReSendLog.Where(x => x.MainMailMessageId == messageId).Select(x => x.ReMailMessageId).ToListAsync();
|
||||
var emailLogQueryable = _emailLogRepository
|
||||
|
||||
.Where(x => reSendMessagelist.Contains(x.MessageId))
|
||||
.ProjectTo<EmailLogView>(_mapper.ConfigurationProvider);
|
||||
|
||||
var defalutSortArray = new string[] { nameof(EmailLogView.EmailDate) + " desc" };
|
||||
var pageList = await emailLogQueryable.ToPagedListAsync(inDto, defalutSortArray);
|
||||
|
||||
return pageList;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取单条邮件日志详情
|
||||
/// </summary>
|
||||
/// <param name="inDto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<GetEmailInfoOutDto> GetEmailInfo(GetEmailInfoInDto inDto)
|
||||
{
|
||||
|
||||
var emailInfo=await _emailLogRepository
|
||||
.Where(x => x.Id == inDto.Id).Include(x=>x.EmailRecipientLogList)
|
||||
.ProjectTo<GetEmailInfoOutDto>(_mapper.ConfigurationProvider).AsNoTracking()
|
||||
.FirstNotNullAsync();
|
||||
|
||||
if (emailInfo.UniqueId.IsNotNullOrEmpty())
|
||||
{
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
await AuthenticateImap(client,inDto.TrialId);
|
||||
var sentFolder = OpenSentFolder(client);
|
||||
var uid = new UniqueId(uint.Parse(emailInfo.UniqueId));
|
||||
var message = sentFolder.GetMessage(uid);
|
||||
emailInfo.Content = message.HtmlBody ?? message.TextBody ?? string.Empty;
|
||||
|
||||
|
||||
if (emailInfo.AttachmentList.Count == 0)
|
||||
{
|
||||
List< EmaliAttachmentInfo > attachmentInfos = new List<EmaliAttachmentInfo>();
|
||||
foreach (var att in message.Attachments)
|
||||
{
|
||||
EmaliAttachmentInfo emaliAttachmentInfo = new EmaliAttachmentInfo();
|
||||
emaliAttachmentInfo.AttachmentName = att.ContentDisposition?.FileName ?? att.ContentType.Name ?? "unknown";
|
||||
|
||||
// 2. 解码后的流直接上传,不落盘
|
||||
if (att is MimePart part)
|
||||
{
|
||||
// 重要:每次上传新建一个独立流,否则迭代过程中流位置会乱
|
||||
await using var decodeStream = new MemoryStream();
|
||||
await part.Content.DecodeToAsync(decodeStream);
|
||||
decodeStream.Position = 0;
|
||||
|
||||
|
||||
emaliAttachmentInfo.AttachmentPath = await oSSService.UploadToOSSAsync(
|
||||
fileStream: decodeStream,
|
||||
oosFolderPath: $"EmailAttachment/{emailInfo.Id}", // OSS 虚拟目录
|
||||
fileRealName: emaliAttachmentInfo.AttachmentName,
|
||||
isFileNameAddGuid: true); // 让方法自己在文件名前加 Guid
|
||||
|
||||
attachmentInfos.Add(emaliAttachmentInfo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
List<EmailAttachmentLog> emailAttachmentLog = attachmentInfos.Select(x => new EmailAttachmentLog()
|
||||
{
|
||||
EmailLogId = emailInfo.Id.Value,
|
||||
AttachmentName = x.AttachmentName,
|
||||
AttachmentPath = x.AttachmentPath,
|
||||
}).ToList();
|
||||
|
||||
await _emailAttachmentLogRepository.AddRangeAsync(emailAttachmentLog);
|
||||
await _emailAttachmentLogRepository.SaveChangesAsync();
|
||||
|
||||
emailInfo.AttachmentList = attachmentInfos;
|
||||
}
|
||||
|
||||
|
||||
sentFolder.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.Disconnect(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", realFileName);
|
||||
|
||||
return emailInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重发邮件
|
||||
/// </summary>
|
||||
/// <param name="inDto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IResponseOutput> ResendEmail(ResendEmailInDto inDto)
|
||||
{
|
||||
var emailInfo = await _emailLogRepository
|
||||
.Where(x => x.Id == inDto.Id)
|
||||
.ProjectTo<GetEmailInfoOutDto>(_mapper.ConfigurationProvider)
|
||||
.FirstNotNullAsync();
|
||||
|
||||
if (emailInfo.UniqueId.IsNotNullOrEmpty())
|
||||
{
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
await AuthenticateImap(client,inDto.TrialId);
|
||||
var sentFolder = OpenSentFolder(client);
|
||||
var uid = new UniqueId(uint.Parse(emailInfo.UniqueId));
|
||||
var message = sentFolder.GetMessage(uid);
|
||||
emailInfo.Content = message.HtmlBody ?? message.TextBody ?? string.Empty;
|
||||
sentFolder.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.Disconnect(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var messageToSend = new MimeMessage();
|
||||
// 发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(emailInfo.SenderName, emailInfo.SenderAddress));
|
||||
|
||||
foreach (var item in emailInfo.RecipientList.Where(x => x.RecipientTypeEnum == RecipientType.To))
|
||||
{
|
||||
messageToSend.To.Add(new MailboxAddress(item.RecipientName, item.RecipientAddress));
|
||||
}
|
||||
foreach (var item in emailInfo.RecipientList.Where(x => x.RecipientTypeEnum == RecipientType.Cc))
|
||||
{
|
||||
messageToSend.Cc.Add(new MailboxAddress(item.RecipientName, item.RecipientAddress));
|
||||
}
|
||||
|
||||
|
||||
messageToSend.Subject = emailInfo.EmailSubject;
|
||||
|
||||
var builder = new BodyBuilder();
|
||||
|
||||
builder.HtmlBody = emailInfo.Content;
|
||||
|
||||
messageToSend.Body = builder.ToMessageBody();
|
||||
|
||||
var msgid= await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
|
||||
|
||||
await _emailReSendLog.AddAsync(new EmailReSendLog()
|
||||
{
|
||||
MainMailMessageId= emailInfo.MessageId,
|
||||
ReMailMessageId= msgid
|
||||
});
|
||||
|
||||
await _emailReSendLog.SaveChangesAsync();
|
||||
|
||||
await SynchronizationEmail(new SynchronizationEmailInDto() {
|
||||
|
||||
TrialId= inDto.TrialId
|
||||
});
|
||||
|
||||
return ResponseOutput.Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步邮件
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IResponseOutput> SynchronizationEmail(SynchronizationEmailInDto inDto)
|
||||
{
|
||||
var maxTime = await _emailLogRepository.MaxAsync(t => t.EmailDate);
|
||||
var startDate = maxTime ?? DateTime.MinValue;
|
||||
List<EmailLog> emailList = new List<EmailLog>();
|
||||
List<EmailRecipientLog> EmailRecipientLogList = new List<EmailRecipientLog>();
|
||||
|
||||
// 第一步:同步发件箱邮件
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
await AuthenticateImap(client, inDto.TrialId);
|
||||
var sentFolder = OpenSentFolder(client);
|
||||
|
||||
var searchQuery = SearchQuery.All.And(SearchQuery.DeliveredAfter(startDate));
|
||||
var uids = sentFolder.Search(searchQuery).ToList();
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = sentFolder.GetMessage(uid);
|
||||
if (message.Date.DateTime <= startDate)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var emaillog = new EmailLog
|
||||
{
|
||||
Id = NewId.NextGuid(),
|
||||
UniqueId = uid.ToString(),
|
||||
MessageId = message.MessageId ?? string.Empty,
|
||||
EmailSubject = message.Subject ?? string.Empty,
|
||||
EmailDate = message.Date.DateTime,
|
||||
EmailStateEnum = EmailState.Success,
|
||||
};
|
||||
|
||||
var fromMailbox = message.From.Mailboxes.FirstOrDefault();
|
||||
if (fromMailbox != null)
|
||||
{
|
||||
emaillog.SenderAddress = fromMailbox.Address;
|
||||
emaillog.SenderName = fromMailbox.Name ?? string.Empty;
|
||||
}
|
||||
|
||||
List<EmailRecipientLog> recipientLogs = new List<EmailRecipientLog>();
|
||||
|
||||
int sort = 0;
|
||||
message.To.Mailboxes.ForEach(x =>
|
||||
{
|
||||
recipientLogs.Add(new EmailRecipientLog()
|
||||
{
|
||||
RecipientName = x.Name ?? string.Empty,
|
||||
RecipientAddress = x.Address,
|
||||
EmailLogId = emaillog.Id,
|
||||
RecipientTypeEnum = RecipientType.To,
|
||||
Sort = sort++,
|
||||
});
|
||||
});
|
||||
sort = 0;
|
||||
message.Cc.Mailboxes.ForEach(x =>
|
||||
{
|
||||
recipientLogs.Add(new EmailRecipientLog()
|
||||
{
|
||||
RecipientName = x.Name ?? string.Empty,
|
||||
RecipientAddress = x.Address,
|
||||
EmailLogId = emaillog.Id,
|
||||
RecipientTypeEnum = RecipientType.Cc,
|
||||
Sort = sort++,
|
||||
});
|
||||
});
|
||||
emailList.Add(emaillog);
|
||||
EmailRecipientLogList.AddRange(recipientLogs);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理邮件 {uid} 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
sentFolder.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"同步发件箱邮件时出错: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.Disconnect(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存同步的发件箱邮件
|
||||
await _emailLogRepository.AddRangeAsync(emailList);
|
||||
await _emailRecipientLogRepository.AddRangeAsync(EmailRecipientLogList);
|
||||
await _emailLogRepository.SaveChangesAsync();
|
||||
|
||||
// 第二步:处理收件箱中的邮件发送失败通知
|
||||
try
|
||||
{
|
||||
await ProcessInboxFailureNotifications(startDate,inDto.TrialId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理收件箱失败通知邮件时出错: {ex.Message}");
|
||||
Console.WriteLine($"异常堆栈: {ex.StackTrace}");
|
||||
// 即使处理失败通知邮件出错,也不影响整体同步操作的成功
|
||||
// 记录警告但不中断主流程
|
||||
}
|
||||
|
||||
return ResponseOutput.Ok();
|
||||
}
|
||||
|
||||
private IMailFolder OpenSentFolder(ImapClient client)
|
||||
{
|
||||
IMailFolder folder = null;
|
||||
try
|
||||
{
|
||||
folder = client.GetFolder(SpecialFolder.Sent);
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (folder == null)
|
||||
{
|
||||
var candidates = new[] { "已发送", "已发送邮件", "Sent", "Sent Items", "[Gmail]/Sent Mail" };
|
||||
foreach (var name in candidates)
|
||||
{
|
||||
try
|
||||
{
|
||||
var f = client.GetFolder(name);
|
||||
if (f != null)
|
||||
{
|
||||
folder = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
if (folder == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var personal = client.GetFolder(client.PersonalNamespaces.FirstOrDefault());
|
||||
if (personal != null)
|
||||
{
|
||||
foreach (var sub in personal.GetSubfolders(false))
|
||||
{
|
||||
if (string.Equals(sub.FullName, "Sent", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(sub.FullName, "Sent Items", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
folder = sub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (folder == null)
|
||||
throw new InvalidOperationException("未找到已发送文件夹");
|
||||
|
||||
folder.Open(FolderAccess.ReadOnly);
|
||||
return folder;
|
||||
}
|
||||
|
||||
private IMailFolder OpenInboxFolder(ImapClient client)
|
||||
{
|
||||
IMailFolder folder = null;
|
||||
try
|
||||
{
|
||||
folder = client.GetFolder(SpecialFolder.All);
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (folder == null)
|
||||
{
|
||||
var candidates = new[] { "收件箱", "Inbox" };
|
||||
foreach (var name in candidates)
|
||||
{
|
||||
try
|
||||
{
|
||||
var f = client.GetFolder(name);
|
||||
if (f != null)
|
||||
{
|
||||
folder = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
if (folder == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var personal = client.GetFolder(client.PersonalNamespaces.FirstOrDefault());
|
||||
if (personal != null)
|
||||
{
|
||||
foreach (var sub in personal.GetSubfolders(false))
|
||||
{
|
||||
if (string.Equals(sub.FullName, "Inbox", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
folder = sub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (folder == null)
|
||||
throw new InvalidOperationException("未找到收件箱文件夹");
|
||||
|
||||
folder.Open(FolderAccess.ReadOnly);
|
||||
return folder;
|
||||
}
|
||||
|
||||
private async Task AuthenticateImap(ImapClient client, Guid? trialId)
|
||||
{
|
||||
try
|
||||
{
|
||||
EmailAuthorization authorization = new EmailAuthorization()
|
||||
{
|
||||
FromEmail = _systemEmailConfig.FromEmail,
|
||||
AuthorizationCode = _systemEmailConfig.AuthorizationCode,
|
||||
};
|
||||
|
||||
if (trialId != null)
|
||||
{
|
||||
authorization = await _trialRepository.Where(x => x.Id == trialId.Value).Select(x => new EmailAuthorization()
|
||||
{
|
||||
AuthorizationCode = x.EmailAuthorizationCode,
|
||||
FromEmail = x.EmailFromEmail
|
||||
}).FirstNotNullAsync();
|
||||
}
|
||||
|
||||
client.Connect(_systemEmailConfig.Imap, _systemEmailConfig.ImapPort, SecureSocketOptions.SslOnConnect);
|
||||
client.Authenticate(authorization.FromEmail, authorization.AuthorizationCode);
|
||||
}
|
||||
catch (AuthenticationException)
|
||||
{
|
||||
//if (_systemEmailConfig.UseOAuth2 && _systemEmailConfig.OAuth2AccessToken.IsNotNullOrEmpty())
|
||||
//{
|
||||
// var sasl = new SaslMechanismOAuth2(_systemEmailConfig.FromEmail, _systemEmailConfig.OAuth2AccessToken);
|
||||
// client.Authenticate(sasl);
|
||||
// return;
|
||||
//}
|
||||
//if (_systemEmailConfig.OAuth2AccessToken.IsNullOrEmpty())
|
||||
//{
|
||||
// var token = AcquireOAuth2TokenByPassword();
|
||||
// if (token.IsNotNullOrEmpty())
|
||||
// {
|
||||
// _systemEmailConfig.OAuth2AccessToken = token;
|
||||
// var sasl = new SaslMechanismOAuth2(_systemEmailConfig.FromEmail, token);
|
||||
// client.Authenticate(sasl);
|
||||
// return;
|
||||
// }
|
||||
//}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理收件箱中的邮件发送失败通知
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task ProcessInboxFailureNotifications(DateTime startTime, Guid? trialId)
|
||||
{
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
await AuthenticateImap(client, trialId);
|
||||
var inboxFolder = OpenInboxFolder(client);
|
||||
|
||||
// 搜索可能包含失败通知的邮件
|
||||
// 通常失败通知邮件会包含"失败"、"错误"、"undeliverable"、"delivery failed"等关键词
|
||||
var searchQuery = SearchQuery.All.Or(SearchQuery.SubjectContains("失败"))
|
||||
.Or(SearchQuery.SubjectContains("错误"))
|
||||
.Or(SearchQuery.SubjectContains("undeliverable"))
|
||||
.Or(SearchQuery.SubjectContains("delivery failed"))
|
||||
.Or(SearchQuery.SubjectContains("Delivery Status Notification"))
|
||||
.Or(SearchQuery.BodyContains("失败"))
|
||||
.Or(SearchQuery.BodyContains("错误"))
|
||||
.Or(SearchQuery.BodyContains("undeliverable"))
|
||||
.Or(SearchQuery.BodyContains("delivery failed")).And(SearchQuery.DeliveredAfter(startTime));
|
||||
|
||||
var uids = inboxFolder.Search(searchQuery).ToList();
|
||||
var processedCount = 0;
|
||||
var updatedCount = 0;
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = inboxFolder.GetMessage(uid);
|
||||
|
||||
// 尝试从邮件内容中提取原始邮件的Message-Id或相关信息
|
||||
var originalMessageId = ExtractOriginalMessageId(message);
|
||||
if (!string.IsNullOrEmpty(originalMessageId))
|
||||
{
|
||||
// 查找对应的EmailLog记录
|
||||
var emailLog = await _emailLogRepository
|
||||
.Where(x => x.MessageId == originalMessageId)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
await _emailLogRepository.BatchUpdateNoTrackingAsync(x => x.MessageId == originalMessageId, x => new EmailLog()
|
||||
{
|
||||
EmailStateEnum = EmailState.Error,
|
||||
ErrorInfo = message.TextBody
|
||||
|
||||
});
|
||||
|
||||
updatedCount++;
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inboxFolder.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"连接收件箱处理失败通知邮件时出错: {ex.Message}");
|
||||
Console.WriteLine($"异常堆栈: {ex.StackTrace}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
client.Disconnect(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"断开IMAP客户端连接时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从失败通知邮件中提取原始邮件的Message-Id
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
private string ExtractOriginalMessageId(MimeMessage message)
|
||||
{
|
||||
// 首先检查邮件头中是否有原始Message-Id
|
||||
var originalMessageId = message.Headers["Original-Message-ID"] ??
|
||||
message.Headers["X-Original-Message-ID"] ??
|
||||
message.Headers["In-Reply-To"];
|
||||
|
||||
if (!string.IsNullOrEmpty(originalMessageId))
|
||||
{
|
||||
// 清理Message-Id格式(移除尖括号)
|
||||
originalMessageId = originalMessageId.Trim('<', '>');
|
||||
return originalMessageId;
|
||||
}
|
||||
|
||||
// 检查邮件附件中是否包含.eml文件(这是最常见的失败通知格式)
|
||||
foreach (var attachment in message.Attachments)
|
||||
{
|
||||
if (attachment is MessagePart messagePart)
|
||||
{
|
||||
// 直接获取嵌入的邮件消息
|
||||
var originalMessageIdFromAttachment = messagePart.Message?.MessageId;
|
||||
if (!string.IsNullOrEmpty(originalMessageIdFromAttachment))
|
||||
{
|
||||
return originalMessageIdFromAttachment.Trim('<', '>');
|
||||
}
|
||||
}
|
||||
else if (attachment is MimePart mimePart)
|
||||
{
|
||||
// 检查文件扩展名是否为.eml
|
||||
var fileName = mimePart.FileName ?? mimePart.ContentType.Name;
|
||||
if (!string.IsNullOrEmpty(fileName) &&
|
||||
(fileName.EndsWith(".eml", StringComparison.OrdinalIgnoreCase) ||
|
||||
mimePart.ContentType.MimeType.Equals("message/rfc822", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
try
|
||||
{
|
||||
// 解析.eml附件内容
|
||||
using var memoryStream = new MemoryStream();
|
||||
mimePart.Content.DecodeTo(memoryStream);
|
||||
memoryStream.Position = 0;
|
||||
|
||||
var originalMessage = MimeMessage.Load(memoryStream);
|
||||
if (!string.IsNullOrEmpty(originalMessage?.MessageId))
|
||||
{
|
||||
return originalMessage.MessageId.Trim('<', '>');
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"解析.eml附件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果邮件头和附件中都没有,尝试从邮件正文中提取
|
||||
var content = message.HtmlBody ?? message.TextBody ?? string.Empty;
|
||||
|
||||
// 尝试匹配常见的Message-Id格式
|
||||
var messageIdPattern = @"(?:Message-ID|Message-Id|message-id):\s*<?([^>\s]+)>?";
|
||||
var match = System.Text.RegularExpressions.Regex.Match(content, messageIdPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||
if (match.Success)
|
||||
{
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
|
||||
// 尝试从邮件引用部分提取
|
||||
var referencePattern = @"^>.*Message-ID:\s*<?([^>\s]+)>?";
|
||||
var matches = System.Text.RegularExpressions.Regex.Matches(content, referencePattern, System.Text.RegularExpressions.RegexOptions.Multiline | System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
return matches[0].Groups[1].Value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<IResponseOutput> AddOrUpdateEmailLog(EmailLogAddOrEdit addOrEditEmailLog)
|
||||
{
|
||||
|
||||
|
||||
|
||||
var entity = await _emailLogRepository.InsertOrUpdateAsync(addOrEditEmailLog, true);
|
||||
|
||||
return ResponseOutput.Ok(entity.Id.ToString());
|
||||
|
||||
}
|
||||
|
||||
|
||||
[HttpDelete("{emailLogId:guid}")]
|
||||
public async Task<IResponseOutput> DeleteEmailLog(Guid emailLogId)
|
||||
{
|
||||
var success = await _emailLogRepository.DeleteFromQueryAsync(t => t.Id == emailLogId,true);
|
||||
return ResponseOutput.Ok();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -4,8 +4,19 @@
|
|||
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using MailKit;
|
||||
using MailKit.Net.Imap;
|
||||
using MailKit.Search;
|
||||
using MailKit.Security;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace IRaCIS.Core.Application.Contracts
|
||||
|
|
@ -15,8 +26,75 @@ namespace IRaCIS.Core.Application.Contracts
|
|||
/// </summary>
|
||||
[ApiExplorerSettings(GroupName = "Common")]
|
||||
public class EmailNoticeConfigService(IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IRepository<EmailNoticeUserType> _emailNoticeUserTypeRepository, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IEmailNoticeConfigService
|
||||
IRepository<EmailNoticeUserType> _emailNoticeUserTypeRepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
|
||||
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IEmailNoticeConfigService
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
|
||||
/// <summary>
|
||||
/// 获取邮件列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<object> GetEmailList()
|
||||
{
|
||||
|
||||
List<IMessageSummary> emailList = new List<IMessageSummary>();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// 连接阿里邮箱 IMAP 服务器(使用 SSL)
|
||||
client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect);
|
||||
|
||||
// 登录
|
||||
client.Authenticate(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode);
|
||||
|
||||
// 3. 获取发件箱文件夹 - 使用你找到的“已发送”
|
||||
var sentFolder = client.GetFolder("已发送");
|
||||
sentFolder.Open(FolderAccess.ReadOnly);
|
||||
|
||||
// 4. 搜索所有邮件(你可以在这里添加更精确的搜索条件)
|
||||
var uids = sentFolder.Search(SearchQuery.All);
|
||||
Console.WriteLine($"找到 {uids.Count} 封已发送邮件");
|
||||
|
||||
// 5. 遍历并处理邮件(示例中处理前10封)
|
||||
foreach (var uid in uids.Take(10))
|
||||
{
|
||||
var message = sentFolder.GetMessage(uid);
|
||||
|
||||
// 输出邮件基本信息
|
||||
Console.WriteLine($"主题: {message.Subject}");
|
||||
Console.WriteLine($"收件人: {string.Join(", ", message.To.Mailboxes.Select(m => m.Address))}");
|
||||
Console.WriteLine($"日期: {message.Date.LocalDateTime:yyyy-MM-dd HH:mm:ss}");
|
||||
Console.WriteLine($"发件人: {message.From}");
|
||||
Console.WriteLine("----------------------------------");
|
||||
}
|
||||
|
||||
sentFolder.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"操作失败: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.Disconnect(true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<PageOutput<EmailNoticeConfigView>> GetEmailNoticeConfigList(EmailNoticeConfigQuery inQuery)
|
||||
|
|
@ -26,7 +104,7 @@ namespace IRaCIS.Core.Application.Contracts
|
|||
.WhereIf(inQuery.SystemLevel != null, t => t.SystemLevel == inQuery.SystemLevel)
|
||||
.WhereIf(inQuery.IsDistinguishCriteria != null, t => t.IsDistinguishCriteria == inQuery.IsDistinguishCriteria)
|
||||
.WhereIf(inQuery.BusinessLevelEnum != null, t => t.BusinessLevelEnum == inQuery.BusinessLevelEnum)
|
||||
.WhereIf(inQuery.CriterionTypeEnum != null, t => t.CriterionTypeEnum == inQuery.CriterionTypeEnum)
|
||||
.WhereIf(inQuery.CriterionTypeEnum != null, t => t.CriterionTypeList.Any(t=>t==inQuery.CriterionTypeEnum))
|
||||
.WhereIf(inQuery.BusinessModuleEnum != null, t => t.BusinessModuleEnum == inQuery.BusinessModuleEnum)
|
||||
.WhereIf(inQuery.BusinessScenarioEnum != null, t => t.BusinessScenarioEnum == inQuery.BusinessScenarioEnum)
|
||||
.WhereIf(inQuery.IsReturnRequired != null, t => t.IsReturnRequired == inQuery.IsReturnRequired)
|
||||
|
|
@ -39,14 +117,83 @@ namespace IRaCIS.Core.Application.Contracts
|
|||
return await emailNoticeConfigQueryable.ToPagedListAsync(inQuery);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量更新邮件主题中英文
|
||||
/// </summary>
|
||||
/// <param name="inCommandList"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IResponseOutput> BatchUpdateEmail(List<BatchUpdateEmailTopicCommand> inCommandList)
|
||||
{
|
||||
var findIdList = inCommandList.Select(x => x.Id).ToList();
|
||||
|
||||
var regex = new Regex(@"\{\s*\d+\s*\}");
|
||||
|
||||
foreach (var inCommand in inCommandList)
|
||||
{
|
||||
if (regex.Matches($"{inCommand.EmailTopic}{inCommand.EmailTopicCN}")
|
||||
.Any(t => t.Value.Contains(" ")))
|
||||
{
|
||||
//邮件模板占位符不允许有空格,请核查占位符的地方
|
||||
return ResponseOutput.NotOk(I18n.T("EmailNoticeConfig_ContainEmpty"));
|
||||
}
|
||||
}
|
||||
|
||||
var list = _emailNoticeConfigrepository.Where(t => findIdList.Contains(t.Id), true).ToList();
|
||||
|
||||
foreach (var item in list)
|
||||
{
|
||||
var exist = inCommandList.FirstOrDefault(t => t.Id == item.Id);
|
||||
if (exist != null)
|
||||
{
|
||||
item.EmailTopic = exist.EmailTopic;
|
||||
item.EmailTopicCN = exist.EmailTopicCN;
|
||||
}
|
||||
}
|
||||
|
||||
await _emailNoticeConfigrepository.SaveChangesAsync();
|
||||
|
||||
|
||||
return ResponseOutput.Ok();
|
||||
}
|
||||
|
||||
public async Task<IResponseOutput> AddOrUpdateEmailNoticeConfig(EmailNoticeConfigAddOrEdit addOrEditEmailNoticeConfig)
|
||||
{
|
||||
var verifyExp1 = new EntityVerifyExp<EmailNoticeConfig>()
|
||||
{
|
||||
VerifyExp = t => t.BusinessScenarioEnum == addOrEditEmailNoticeConfig.BusinessScenarioEnum && t.CriterionTypeEnum== addOrEditEmailNoticeConfig.CriterionTypeEnum,
|
||||
//var verifyExp1 = new EntityVerifyExp<EmailNoticeConfig>()
|
||||
//{
|
||||
// VerifyExp = t => t.BusinessScenarioEnum == addOrEditEmailNoticeConfig.BusinessScenarioEnum && t.CriterionTypeEnum == addOrEditEmailNoticeConfig.CriterionTypeEnum,
|
||||
|
||||
VerifyMsg = _localizer["EmailNoticeConfig_RepeatEmailScenario"]
|
||||
// VerifyMsg = _localizer["EmailNoticeConfig_RepeatEmailScenario"]
|
||||
|
||||
//};
|
||||
|
||||
var criterionList = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == addOrEditEmailNoticeConfig.BusinessScenarioEnum && t.IsEnable == true)
|
||||
.Where(t => t.CriterionTypeList != null)
|
||||
.WhereIf(addOrEditEmailNoticeConfig.Id != null, t => t.Id != addOrEditEmailNoticeConfig.Id)
|
||||
.Select(t => t.CriterionTypeList).ToList();//不能使用selectMany 会当成关联对象,不能当成字符串
|
||||
|
||||
|
||||
if (addOrEditEmailNoticeConfig.CriterionTypeList != null)
|
||||
{
|
||||
foreach (var item in addOrEditEmailNoticeConfig.CriterionTypeList)
|
||||
{
|
||||
foreach (var itemList in criterionList)
|
||||
{
|
||||
if (itemList.Any(t => t == item))
|
||||
{
|
||||
return ResponseOutput.NotOk(_localizer["EmailNoticeConfig_RepeatEmailScenario"]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var verifyExp2 = new EntityVerifyExp<EmailNoticeConfig>()
|
||||
{
|
||||
VerifyExp = t => t.Code == addOrEditEmailNoticeConfig.Code && t.IsDistinguishCriteria == addOrEditEmailNoticeConfig.IsDistinguishCriteria && t.SystemLevel == addOrEditEmailNoticeConfig.SystemLevel,
|
||||
|
||||
VerifyMsg = _localizer["EmailNoticeConfig_RepeatCode"]
|
||||
|
||||
};
|
||||
|
||||
|
|
@ -61,10 +208,14 @@ namespace IRaCIS.Core.Application.Contracts
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
var entity = new EmailNoticeConfig() { };
|
||||
|
||||
if (addOrEditEmailNoticeConfig.Id == null)
|
||||
{
|
||||
|
||||
var entity = _mapper.Map<EmailNoticeConfig>(addOrEditEmailNoticeConfig);
|
||||
entity = _mapper.Map<EmailNoticeConfig>(addOrEditEmailNoticeConfig);
|
||||
|
||||
|
||||
foreach (var item in addOrEditEmailNoticeConfig.ToUserTypeList)
|
||||
|
|
@ -80,9 +231,9 @@ namespace IRaCIS.Core.Application.Contracts
|
|||
}
|
||||
|
||||
|
||||
await _emailNoticeConfigrepository.AddAsync(entity, true, verifyExp1);
|
||||
await _emailNoticeConfigrepository.AddAsync(entity, true, verifyExp2);
|
||||
|
||||
|
||||
return ResponseOutput.Ok(entity.Id.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -103,14 +254,32 @@ namespace IRaCIS.Core.Application.Contracts
|
|||
}
|
||||
|
||||
|
||||
var entity = await _emailNoticeConfigrepository.UpdateFromDTOAsync(addOrEditEmailNoticeConfig, true, false, verifyExp1);
|
||||
entity = await _emailNoticeConfigrepository.UpdateFromDTOAsync(addOrEditEmailNoticeConfig, true, false, verifyExp2);
|
||||
|
||||
|
||||
return ResponseOutput.Ok(entity.Id.ToString());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (addOrEditEmailNoticeConfig.EmailCron != string.Empty)
|
||||
{
|
||||
var jobId = $"{entity.Id}_({addOrEditEmailNoticeConfig.BusinessScenarioEnum})";
|
||||
|
||||
HangfireJobHelper.RemoveCronJob(jobId);
|
||||
|
||||
//有的job 可能编辑控制直接不发,需要移除已存在的
|
||||
if (entity.IsAutoSend && entity.IsEnable)
|
||||
{
|
||||
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, addOrEditEmailNoticeConfig.BusinessScenarioEnum, addOrEditEmailNoticeConfig.EmailCron);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return ResponseOutput.Ok(entity.Id.ToString());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue