Compare commits

...

353 Commits

Author SHA1 Message Date
Mrx
2fff537bfc f 2026-06-12 14:45:09 +08:00
Mrx
97f17e0cb7 f 2026-06-12 14:40:57 +08:00
Mrx
ad44b190b7 f 2026-06-12 11:03:40 +08:00
Mrx
1c7b00e29c f 2026-06-11 18:00:07 +08:00
Mrx
47ade3de94 f 2026-06-11 16:39:46 +08:00
Mrx
0642d92069 f 2026-06-11 10:53:22 +08:00
Mrx
0780ddfff8 f 2026-06-11 10:36:44 +08:00
Mrx
e8fef3b832 fadd 2026-06-11 10:31:24 +08:00
Mrx
3e0b100bbf f 2026-06-11 10:24:49 +08:00
Mrx
d5fa2f0a15 f 2026-06-10 21:12:37 +08:00
Mrx
0cfcd6a7ee f 2026-06-10 20:27:18 +08:00
Mrx
66e8bb267d f 2026-06-10 17:47:36 +08:00
Mrx
9d4a3f584c f 2026-06-10 16:05:47 +08:00
Mrx
50f21283e8 f 2026-06-09 16:46:01 +08:00
Mrx
21f7643b06 f 2026-06-09 16:44:10 +08:00
Mrx
fd1f83a0f0 f 2026-06-09 16:28:08 +08:00
Mrx
6d36c86c0f f 2026-06-09 16:20:33 +08:00
Mrx
9f06027820 f 2026-06-09 15:58:29 +08:00
Mrx
a90dfb90ef add f 2026-06-09 11:28:47 +08:00
Mrx
11d93067a2 f 2026-06-08 15:54:05 +08:00
Mrx
937c0a29ff add 2026-06-08 14:56:38 +08:00
Mrx
57ca7be795 f++ 2026-06-08 12:06:07 +08:00
Mrx
a4b2d493db Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-06-07 21:49:12 +08:00
Mrx
0deefd9b0e tax_income_level_v8 2026-06-07 21:49:11 +08:00
32f9299576 f 2026-06-07 12:52:10 +08:00
59c09d6a33 f 2026-06-06 14:45:22 +08:00
Mrx
e4eb41ce10 f 2026-06-05 21:11:25 +08:00
Mrx
85212f4dd2 f 2026-06-04 22:53:59 +08:00
Mrx
962599d970 f 2026-06-04 22:18:06 +08:00
Mrx
4b78edc071 f 2026-06-04 15:01:15 +08:00
Mrx
f08a940807 ff 2026-06-04 12:21:44 +08:00
Mrx
1a1d98194e f 2026-06-04 11:07:39 +08:00
Mrx
be8b516483 f 2026-06-04 11:06:13 +08:00
Mrx
486e586ea1 f 2026-06-02 15:33:41 +08:00
Mrx
6d892643ba f 2026-06-02 12:26:22 +08:00
Mrx
aed109d589 f 2026-06-01 20:47:44 +08:00
Mrx
8ab2a6d81d f 2026-06-01 14:39:45 +08:00
Mrx
928ff4d766 f 2026-05-30 11:13:17 +08:00
Mrx
5ee816d6c1 fix 2026-05-29 21:13:46 +08:00
Mrx
4b1176b1e3 f 2026-05-29 20:13:12 +08:00
Mrx
2a174e49e5 ff 2026-05-29 17:51:30 +08:00
Mrx
a5a0522c91 f 2026-05-29 17:06:27 +08:00
Mrx
a5c955d04a Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-05-29 15:41:53 +08:00
Mrx
708f42fbd1 f 2026-05-29 15:41:52 +08:00
636d9eaf7a Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-05-29 12:37:38 +08:00
0d3a116820 f 2026-05-29 12:37:37 +08:00
Mrx
3c2cb5b6ab Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-05-29 12:28:09 +08:00
Mrx
0973bb099c f 2026-05-29 12:28:08 +08:00
f8bf88f635 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-05-28 12:23:05 +08:00
8a9b87be0e f 2026-05-28 12:23:04 +08:00
Mrx
43acbeb8f4 f 2026-05-28 10:55:28 +08:00
Mrx
b04b43cb82 f 2026-05-27 15:30:59 +08:00
Mrx
6cde82ee69 f 2026-05-27 15:18:43 +08:00
Mrx
b16e68f3bd f 2026-05-27 14:16:41 +08:00
Mrx
f2c4618649 f 2026-05-27 14:12:11 +08:00
Mrx
9e136bbd6b f 2026-05-27 13:10:00 +08:00
Mrx
5cee8ff035 f 2026-05-27 13:00:51 +08:00
Mrx
b7fb2a73c9 f 2026-05-27 12:11:26 +08:00
Mrx
2a33369639 f 2026-05-26 18:09:49 +08:00
Mrx
28fc40525e f 2026-05-26 16:25:11 +08:00
Mrx
4fe180e4c8 f 2026-05-26 16:21:55 +08:00
Mrx
f10c5dd626 f 2026-05-26 16:17:09 +08:00
Mrx
760c5812ee f 2026-05-26 11:28:25 +08:00
Mrx
9bd83ddaba f 2026-05-26 11:21:58 +08:00
Mrx
1cb3363b5c f 2026-05-26 11:14:24 +08:00
Mrx
95c3fc0315 f 2026-05-23 10:30:17 +08:00
Mrx
2020bd732a f 2026-05-21 10:27:41 +08:00
Mrx
1dfe688db7 f 2026-05-21 10:27:05 +08:00
Mrx
9fb5db56e3 f+ 2026-05-20 20:30:50 +08:00
Mrx
95006a2455 f 2026-05-20 16:16:50 +08:00
Mrx
a2913c26ab f 2026-05-20 15:52:04 +08:00
Mrx
40f12ac1cf f 2026-05-19 17:21:16 +08:00
Mrx
8c301de7d2 f 2026-05-11 13:36:49 +08:00
Mrx
38410a7378 f 2026-05-11 13:24:59 +08:00
Mrx
a55d881a3a f 2026-05-11 13:06:25 +08:00
Mrx
be446de86d f 2026-05-11 12:13:32 +08:00
Mrx
07394a4ffa f 2026-05-10 13:36:47 +08:00
Mrx
609a35fad6 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-05-09 21:44:55 +08:00
Mrx
f927f75637 f 2026-05-09 21:44:50 +08:00
ff66c341f5 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-05-09 19:08:04 +08:00
04a683273c f 2026-05-09 19:08:02 +08:00
Mrx
3c71431e55 f 2026-05-09 18:50:45 +08:00
Mrx
b22fdd297b f 2026-05-08 12:07:05 +08:00
Mrx
dfd7e86f6c f 2026-05-07 12:39:37 +08:00
Mrx
1ca6e1c81d f 2026-05-06 16:03:18 +08:00
Mrx
c8a5e6a908 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-05-01 15:22:20 +08:00
Mrx
1ef1da070f f 2026-05-01 15:22:18 +08:00
45b0ddcf9f Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-05-01 11:28:48 +08:00
40f4fab1d4 f 2026-05-01 11:28:46 +08:00
Mrx
c10a54c5b3 f 2026-04-30 10:28:34 +08:00
Mrx
1ca9885e38 f 2026-04-29 16:32:17 +08:00
Mrx
e08a25ba5a f 2026-04-29 16:07:40 +08:00
Mrx
4e33927255 ff 2026-04-29 16:07:31 +08:00
Mrx
af91d8c7f3 f 2026-04-29 16:04:55 +08:00
98a78c3170 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-04-28 12:25:53 +08:00
3b7bf1052e add huibo ivyz4y27 2026-04-28 12:25:41 +08:00
Mrx
6253389d1b f 2026-04-28 11:25:29 +08:00
Mrx
1b6d59a55d Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-04-27 21:15:16 +08:00
Mrx
972d1a0217 f 2026-04-27 21:15:15 +08:00
4f64c22ebc f 2026-04-27 18:21:37 +08:00
20b63bde0c f 2026-04-27 18:14:07 +08:00
fcbd534b57 f 2026-04-25 21:00:22 +08:00
d564f4eb1b f 2026-04-25 20:44:34 +08:00
e89459f093 f 2026-04-25 20:36:28 +08:00
18c92584d9 f 2026-04-25 19:17:19 +08:00
ba463ae38d f 2026-04-25 11:59:10 +08:00
Mrx
e246271a24 f 2026-04-23 18:18:47 +08:00
Mrx
a1024ed4b2 f 2026-04-23 17:59:23 +08:00
Mrx
d6b78a5d6d Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-04-23 10:55:02 +08:00
Mrx
61c6cc4f35 370982199012037272 2026-04-23 10:55:01 +08:00
cdd1e00745 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-04-21 21:03:56 +08:00
46ba4e048c f 2026-04-21 21:02:02 +08:00
Mrx
3156539319 f 2026-04-21 17:03:03 +08:00
Mrx
dad8abad16 f 2026-04-21 16:24:57 +08:00
Mrx
5f62261c11 f 2026-04-21 16:23:35 +08:00
a0b2105339 f 2026-04-20 19:41:29 +08:00
83e71ae81b f 2026-04-20 19:11:49 +08:00
8675961207 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-04-20 18:45:35 +08:00
4bd6f51728 f 2026-04-20 18:45:34 +08:00
Mrx
cd1db5276a f 2026-04-20 16:58:19 +08:00
Mrx
2f653be375 f 2026-04-20 15:22:25 +08:00
Mrx
9c3fb97b3f f 2026-04-20 10:27:55 +08:00
Mrx
b6053983d9 f 2026-04-18 17:45:18 +08:00
Mrx
c3b16c0ffe ff 2026-04-18 16:41:06 +08:00
Mrx
5f6cca5369 f 2026-04-17 18:41:54 +08:00
Mrx
a01226c7c0 f 2026-04-17 18:37:19 +08:00
Mrx
e67465a58d Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-04-16 19:32:23 +08:00
Mrx
75316b10cb f 2026-04-16 19:32:22 +08:00
ebcf3be923 f 2026-04-14 19:23:00 +08:00
cff3fb8814 f 2026-04-11 14:59:07 +08:00
e76fcd89bb f 2026-04-08 19:29:36 +08:00
10605afe1e f 2026-04-08 19:25:36 +08:00
d3554e8b44 f 2026-04-07 13:55:50 +08:00
Mrx
35a2eb03d8 f 2026-04-07 12:54:44 +08:00
Mrx
2a6ec6e3ca f 2026-04-07 12:39:11 +08:00
Mrx
44d8b3d28c f 2026-04-07 12:35:54 +08:00
Mrx
cad1d354f5 f 2026-04-03 11:36:19 +08:00
Mrx
711dc83e47 f 2026-04-02 14:45:48 +08:00
Mrx
65fdc9bf21 addf 2026-04-02 14:01:19 +08:00
Mrx
e9fe7ac303 f 2026-04-02 12:46:42 +08:00
Mrx
130f49fb9d f 2026-04-01 14:18:23 +08:00
Mrx
d66ef0b15f f 2026-03-30 10:57:32 +08:00
Mrx
a6a2d8d9c5 f 2026-03-26 16:08:48 +08:00
Mrx
e095553ba8 f 2026-03-26 15:31:37 +08:00
Mrx
a73097aed3 add 2026-03-26 11:31:11 +08:00
Mrx
8bbd098f97 f 2026-03-25 15:17:24 +08:00
Mrx
5f0224ad3b f 2026-03-25 15:03:45 +08:00
Mrx
9438ccee5e f 2026-03-25 11:07:58 +08:00
Mrx
8771261118 f 2026-03-24 18:23:59 +08:00
Mrx
96c5870aa0 f 2026-03-24 12:18:00 +08:00
Mrx
5e658f2527 add newapilist 2026-03-23 15:34:27 +08:00
Mrx
e03e6b983c add hotapi 2026-03-23 15:29:37 +08:00
Mrx
6ab9bb21e7 f 2026-03-23 14:08:35 +08:00
Mrx
7c4bcefc81 f 2026-03-23 12:59:45 +08:00
Mrx
8eec9685db f 2026-03-23 12:36:23 +08:00
6a801acee1 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-21 19:23:23 +08:00
6120020a7c f 2026-03-21 19:23:08 +08:00
Mrx
da0990e015 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-21 19:17:19 +08:00
Mrx
80faf3cac0 f 2026-03-21 19:17:17 +08:00
df1e8f25ed f 2026-03-21 19:14:52 +08:00
bfe2f065c5 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-21 19:11:11 +08:00
2fcf55deee f 2026-03-21 19:10:50 +08:00
Mrx
9a1cf0d1d1 f 2026-03-21 19:08:11 +08:00
Mrx
895b38ab88 f 2026-03-21 19:01:26 +08:00
Mrx
a96c153286 f 2026-03-21 18:56:30 +08:00
Mrx
040f6eef65 f 2026-03-21 18:51:51 +08:00
Mrx
947a983c67 f 2026-03-21 18:47:04 +08:00
Mrx
df6a51ae62 f 2026-03-21 18:36:42 +08:00
Mrx
06b5aa97ec f 2026-03-21 18:32:55 +08:00
Mrx
3775101081 f 2026-03-21 15:21:57 +08:00
Mrx
39db1e9c1d f 2026-03-21 15:19:24 +08:00
Mrx
15c6257762 f 2026-03-21 14:37:40 +08:00
Mrx
f9a6204b40 up 2026-03-20 18:45:47 +08:00
Mrx
4de32c4c39 verify 2026-03-20 17:14:35 +08:00
Mrx
a6f309e472 f 2026-03-20 16:23:08 +08:00
Mrx
c27b15af18 f 2026-03-20 13:27:08 +08:00
Mrx
249ea0b15a f 2026-03-20 13:26:47 +08:00
Mrx
c193211463 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-20 13:25:47 +08:00
Mrx
521bfeb4ef add 2026-03-20 13:24:45 +08:00
e0d9fd2791 f 2026-03-19 18:02:34 +08:00
58ba7e9f70 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-19 17:47:11 +08:00
Mrx
3779a7d66d f 2026-03-19 17:29:06 +08:00
Mrx
8eb6dfc962 f 2026-03-19 15:21:02 +08:00
Mrx
c8af22f981 f 2026-03-19 13:25:00 +08:00
Mrx
d837624c0a f 2026-03-19 13:23:48 +08:00
Mrx
faf4b7f6a7 f 2026-03-19 11:07:52 +08:00
Mrx
baa45a8a05 f 2026-03-18 16:45:23 +08:00
Mrx
ec1decfdd9 f 2026-03-18 16:27:16 +08:00
Mrx
ca45be642b f 2026-03-18 16:05:01 +08:00
Mrx
bba34f817e f 2026-03-18 13:30:19 +08:00
Mrx
4e8f9317f5 f 2026-03-18 13:20:41 +08:00
Mrx
ce2d4087bb f 2026-03-18 12:51:54 +08:00
Mrx
0ce793ac61 f and add 2026-03-18 11:44:37 +08:00
Mrx
12ed1c81e3 add 2026-03-17 17:18:54 +08:00
Mrx
6f0a8e0519 f 2026-03-16 13:10:42 +08:00
Mrx
14b2c53eeb f 2026-03-16 12:32:41 +08:00
Mrx
09db8d003e f 2026-03-13 18:21:09 +08:00
Mrx
209ffec51d f 2026-03-13 18:07:24 +08:00
Mrx
f16274d1e9 f 2026-03-12 14:35:13 +08:00
869b269fb1 f 2026-03-11 19:36:26 +08:00
9e76fd467b f 2026-03-11 19:19:18 +08:00
1a5e771420 f 2026-03-11 19:10:55 +08:00
2114f602de f 2026-03-11 18:12:14 +08:00
5650e78254 f 2026-03-11 18:05:10 +08:00
67c6e2e144 f 2026-03-11 17:23:09 +08:00
ba1a72aa8f f 2026-03-11 15:35:50 +08:00
058e355d77 f 2026-03-11 15:31:36 +08:00
2741839cf3 f 2026-03-11 15:27:32 +08:00
454e60dd72 f 2026-03-11 15:21:53 +08:00
03cb6fd92b f 2026-03-11 15:00:25 +08:00
8441e66e93 f 2026-03-11 14:55:45 +08:00
16a2e4ff09 f 2026-03-11 14:13:57 +08:00
c5970da195 f 2026-03-10 19:15:54 +08:00
1bcb4a9c2e f 2026-03-10 19:12:35 +08:00
8877cf9691 f 2026-03-10 19:07:29 +08:00
f63e6df9f9 f 2026-03-10 19:03:42 +08:00
a00fe12141 f 2026-03-10 17:56:31 +08:00
6b80182986 f 2026-03-10 17:49:53 +08:00
bf4c114ee2 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-10 17:28:22 +08:00
4cd3954574 f 2026-03-10 17:28:04 +08:00
Mrx
d7a5589873 f 2026-03-09 11:31:40 +08:00
Mrx
b0ec75d1af f 2026-03-06 16:39:00 +08:00
Mrx
57d18be972 f 2026-03-06 16:28:25 +08:00
Mrx
3d8775b6dc f 2026-03-06 15:20:27 +08:00
Mrx
f40950f890 f 2026-03-06 15:12:58 +08:00
Mrx
ba21a8f965 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-06 10:56:37 +08:00
Mrx
7d2716da7a f 2026-03-06 10:56:36 +08:00
9a7bda9527 f 2026-03-05 19:03:18 +08:00
abdae033f0 f 2026-03-05 18:44:17 +08:00
96abacd392 f 2026-03-05 18:41:00 +08:00
4e6c93413e Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-05 17:44:59 +08:00
9c8dbd458f f 2026-03-05 17:44:50 +08:00
Mrx
9e9cee02f5 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-05 16:24:56 +08:00
Mrx
360bd579ce f 2026-03-05 16:24:53 +08:00
db889ccba0 f 2026-03-05 14:17:48 +08:00
25a4961328 f 2026-03-05 12:26:02 +08:00
Mrx
578e68a76b f 2026-03-05 11:05:01 +08:00
Mrx
019e47896d f 2026-03-05 10:57:06 +08:00
Mrx
c0898e6829 f 2026-03-05 10:54:16 +08:00
Mrx
4ee6e891cd f 2026-03-04 14:10:30 +08:00
Mrx
44b5f6b145 f 2026-03-04 13:20:44 +08:00
Mrx
677b7362cf f 2026-03-04 13:19:55 +08:00
Mrx
02dbc02fe8 f 2026-03-04 12:59:45 +08:00
Mrx
374143995e f 2026-03-04 12:41:08 +08:00
Mrx
7a957a6b87 f 2026-03-04 12:39:01 +08:00
Mrx
c885d562ee f 2026-03-02 19:21:23 +08:00
Mrx
9f36cd8b63 f 2026-03-02 12:21:25 +08:00
Mrx
4122f874fc f 2026-03-02 12:16:27 +08:00
Mrx
9a32387b21 f 2026-03-02 11:56:47 +08:00
Mrx
7bf9150cfc f 2026-03-02 11:39:53 +08:00
Mrx
fecd5a38fd Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-02 11:38:30 +08:00
Mrx
2636d9dff6 f 2026-03-02 11:38:19 +08:00
927b08b871 f 2026-02-28 14:49:42 +08:00
dedd4a60a4 f 2026-02-28 14:05:54 +08:00
a54a19e439 f 2026-02-27 16:49:45 +08:00
6dd392f673 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-02-27 16:43:06 +08:00
8d5da9d88e f 2026-02-27 16:42:38 +08:00
Mrx
bc6dce21ee f 2026-02-27 14:59:23 +08:00
Mrx
5630d93de6 f 2026-02-27 14:53:08 +08:00
Mrx
d12529307b add 2026-02-27 14:49:29 +08:00
f17e22f4c8 f 2026-02-25 19:33:56 +08:00
8c0c16006e f 2026-02-25 17:50:28 +08:00
532503ffe3 f 2026-02-25 17:45:14 +08:00
af37f8620c f 2026-02-25 14:54:49 +08:00
fb495bc0ca f 2026-02-14 17:42:59 +08:00
2c57ac0dab Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-02-14 17:26:15 +08:00
4b437ecf56 f 2026-02-14 17:26:01 +08:00
Mrx
abf6284482 open judiciaRiskInfos 2026-02-14 16:06:28 +08:00
e6ab833099 f 2026-02-12 13:43:03 +08:00
47cbc5b3a5 f 2026-02-12 13:36:24 +08:00
f400052f95 f 2026-02-12 13:27:08 +08:00
Mrx
a38c58c357 de|| paramsDto.IDCard == "320682198910134998" 2026-02-12 12:48:36 +08:00
Mrx
ede446fb90 f 2026-02-12 11:35:09 +08:00
e23897a13f f 2026-02-11 20:15:24 +08:00
debd0a973f f 2026-02-11 20:12:58 +08:00
b424082f01 f 2026-02-11 20:08:27 +08:00
4afbd7dec7 f 2026-02-11 19:39:27 +08:00
070310cfa7 f 2026-02-11 19:15:38 +08:00
Mrx
62e220a7b8 f 2026-02-10 18:00:20 +08:00
Mrx
c37cf2b54a f 2026-02-10 17:37:15 +08:00
Mrx
25a3b4a761 f 2026-02-10 15:36:48 +08:00
Mrx
1c83102e06 f 2026-02-10 14:32:58 +08:00
Mrx
d55520e69c f 2026-02-10 11:47:24 +08:00
Mrx
4ea7cf4ecb ff 2026-02-10 11:27:33 +08:00
Mrx
3eae08a576 f 2026-02-08 12:32:26 +08:00
Mrx
c9126dd780 ff 2026-02-08 11:54:32 +08:00
Mrx
f48289b32b f 2026-01-31 17:43:31 +08:00
Mrx
0091e01574 f 2026-01-31 12:27:11 +08:00
Mrx
d2b806eda0 f 2026-01-31 12:20:32 +08:00
Mrx
ff86cb6fb9 f 2026-01-31 12:15:42 +08:00
Mrx
a6f858dbd3 f 2026-01-31 11:32:53 +08:00
Mrx
6a1a59de8d f 2026-01-30 18:32:16 +08:00
Mrx
dbcecde4e0 f 2026-01-30 18:25:30 +08:00
Mrx
3158bf8c04 f 2026-01-29 17:10:44 +08:00
Mrx
860756b767 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-01-29 16:51:15 +08:00
Mrx
3c90144d51 f 2026-01-29 16:51:11 +08:00
29c49d6a00 f 2026-01-29 16:40:19 +08:00
5bff33547c f 2026-01-29 16:34:37 +08:00
f50e11a052 f 2026-01-29 16:12:42 +08:00
d1e06984ac f 2026-01-29 16:00:51 +08:00
2325a110b6 f 2026-01-29 15:48:07 +08:00
167dd63a7a f 2026-01-29 15:35:25 +08:00
360fed3907 f 2026-01-29 15:27:45 +08:00
88787c6145 f 2026-01-29 15:25:30 +08:00
bfa8bbcfcb Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-01-29 15:03:42 +08:00
2fea046981 f 2026-01-29 15:03:38 +08:00
Mrx
4e359060e6 无缝替换西部 2026-01-28 16:58:57 +08:00
Mrx
7e5a69ffaa f 2026-01-28 16:25:40 +08:00
Mrx
482644a914 f 2026-01-28 16:21:57 +08:00
Mrx
b2f0b47896 f 去掉66sl 2026-01-28 15:55:38 +08:00
Mrx
b1f573c230 f 2026-01-28 12:21:21 +08:00
2363a51a6a f 2026-01-27 19:12:02 +08:00
847d48d276 f 2026-01-27 19:00:09 +08:00
43e4daa45b f 2026-01-27 18:12:38 +08:00
2005f09248 f 2026-01-27 17:33:09 +08:00
eb8886d961 f 2026-01-27 16:50:13 +08:00
411aeb8e25 f 2026-01-27 16:42:25 +08:00
f8806eb71c f 2026-01-27 16:26:48 +08:00
Mrx
3ef7b7d1fb f 2026-01-26 18:14:24 +08:00
32336e4ba0 f 2026-01-25 14:35:50 +08:00
Mrx
168a7c7f5f f 2026-01-24 15:38:15 +08:00
Mrx
ebaff673c7 f 2026-01-24 14:13:18 +08:00
Mrx
1cfd4bf0d0 f 2026-01-24 12:28:49 +08:00
Mrx
21d312f143 f 2026-01-24 11:48:45 +08:00
Mrx
37ce65d6f7 f 2026-01-24 11:36:41 +08:00
Mrx
4664c06c03 f 2026-01-24 11:02:05 +08:00
Mrx
ea59a8ddff f 2026-01-24 10:44:09 +08:00
Mrx
b171288361 f 2026-01-24 10:25:13 +08:00
Mrx
426e6f537c f 2026-01-24 10:22:43 +08:00
Mrx
bce7ba9ef4 f 2026-01-23 19:05:57 +08:00
Mrx
da1eef618e f 2026-01-23 19:03:01 +08:00
Mrx
791147e520 f 2026-01-23 18:58:40 +08:00
Mrx
82959f74a8 f 2026-01-23 18:50:52 +08:00
Mrx
6fccd4b223 f 2026-01-23 18:39:11 +08:00
Mrx
e48efb4566 f 2026-01-23 18:37:22 +08:00
Mrx
e6e38013b8 f 2026-01-23 18:33:14 +08:00
Mrx
9b223c996d f 2026-01-23 18:18:49 +08:00
Mrx
78035ca1ab 替换shumai 2026-01-23 18:10:07 +08:00
Mrx
38ca033e31 f 2026-01-23 18:02:39 +08:00
Mrx
b05c459b81 f 2026-01-23 17:53:11 +08:00
Mrx
6104bfb84f f 2026-01-22 18:10:21 +08:00
Mrx
d15d2f2499 f 2026-01-22 17:31:32 +08:00
Mrx
7e45a45309 f 2026-01-22 14:44:39 +08:00
Mrx
473468e680 f 2026-01-22 14:26:15 +08:00
Mrx
7785d3b6ef f 2026-01-22 12:23:59 +08:00
Mrx
6607129083 f 2026-01-21 18:02:31 +08:00
Mrx
0036180979 s 2026-01-21 18:00:38 +08:00
Mrx
0d3a006c46 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-01-21 16:12:58 +08:00
Mrx
17f2433fee 重新启用QYGL3F8E upQYGL6S1B addFXGL5a3b dQYGL8271 2026-01-21 16:10:23 +08:00
8a222a0b7f f 2026-01-20 18:32:16 +08:00
03cfddee93 f 2026-01-20 17:31:35 +08:00
Mrx
abc7d655ce -f 2026-01-20 14:26:47 +08:00
Mrx
bdb8395615 -f 2026-01-20 14:25:50 +08:00
377 changed files with 118274 additions and 4536 deletions

View File

@@ -37,12 +37,27 @@ FROM alpine:3.19
# 设置Alpine镜像源 # 设置Alpine镜像源
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
# 安装必要的包 # 安装必要的包(包含 headless Chrome 所需依赖)
RUN apk --no-cache add tzdata curl # - tzdata: 时区
# - curl: 健康检查
# - chromium: 无头浏览器,用于 chromedp 生成 HTML 报告 PDF
# - nss、freetype、harfbuzz、ttf-freefont、font-noto-cjk: 字体及渲染依赖,避免中文/图标丢失和乱码
RUN apk --no-cache add \
tzdata \
curl \
chromium \
nss \
freetype \
harfbuzz \
ttf-freefont \
font-noto-cjk
# 设置时区 # 设置时区
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai
# 为 chromedp 指定默认的 Chrome 路径Alpine 下 chromium 包的可执行文件)
ENV CHROME_BIN=/usr/bin/chromium-browser
# 设置工作目录 # 设置工作目录
WORKDIR /app WORKDIR /app
@@ -53,9 +68,8 @@ COPY --from=builder /app/tyapi-server .
COPY config.yaml . COPY config.yaml .
COPY configs/ ./configs/ COPY configs/ ./configs/
# 复制资源文件(直接从构建上下文复制,与配置文件一致 # 复制资源文件(报告模板、PDF、组件等
COPY resources/etc ./resources/etc COPY resources ./resources
COPY resources/pdf ./resources/pdf
# 暴露端口 # 暴露端口
EXPOSE 8080 EXPOSE 8080

View File

@@ -0,0 +1,79 @@
// 将 raw fixture各处理器原始 JSON经 BuildReportFromRawSources 转化为与线上一致的完整报告 JSON。
//
// go run ./cmd/qygl_report_build -in resources/dev-report/fixture.raw.example.json -out resources/dev-report/built.json
// go run ./cmd/qygl_report_build -in raw.json -out - # 输出到 stdout
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"tyapi-server/internal/domains/api/services/processors/qygl"
)
type rawBundle struct {
Kind string `json:"kind"`
JiguangFull map[string]interface{} `json:"jiguangFull"`
JudicialCertFull map[string]interface{} `json:"judicialCertFull"`
EquityPanorama map[string]interface{} `json:"equityPanorama"`
AnnualReport map[string]interface{} `json:"annualReport"`
TaxViolation map[string]interface{} `json:"taxViolation"`
TaxArrears map[string]interface{} `json:"taxArrears"`
CustomsCredit map[string]interface{} `json:"customsCredit"`
}
func main() {
inPath := flag.String("in", "", "raw fixture JSON 路径(含 jiguangFull 等字段,可参考 fixture.raw.example.json")
outPath := flag.String("out", "", "输出文件;- 或留空表示输出到 stdout")
flag.Parse()
if *inPath == "" {
log.Fatal("请指定 -in <raw.json>")
}
raw, err := os.ReadFile(*inPath)
if err != nil {
log.Fatalf("读取输入失败: %v", err)
}
var b rawBundle
if err := json.Unmarshal(raw, &b); err != nil {
log.Fatalf("解析 JSON 失败: %v", err)
}
if b.Kind == "full" {
log.Fatal("输入为 kind=full已是 build 结果),无需再转化;预览请用: go run ./cmd/qygl_report_preview")
}
if b.Kind != "" && b.Kind != "raw" {
log.Fatalf("若填写 kind仅支持 raw当前: %q", b.Kind)
}
report := qygl.BuildReportFromRawSources(
b.JiguangFull,
b.JudicialCertFull,
b.EquityPanorama,
b.AnnualReport,
b.TaxViolation,
b.TaxArrears,
b.CustomsCredit,
)
out, err := json.MarshalIndent(report, "", " ")
if err != nil {
log.Fatalf("序列化报告失败: %v", err)
}
if *outPath == "" || *outPath == "-" {
if _, err := os.Stdout.Write(append(out, '\n')); err != nil {
log.Fatal(err)
}
return
}
if err := os.WriteFile(*outPath, append(out, '\n'), 0644); err != nil {
log.Fatalf("写入失败: %v", err)
}
fmt.Fprintf(os.Stderr, "已写入 %s\n", *outPath)
}

View File

@@ -0,0 +1,60 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
"go.uber.org/zap"
"tyapi-server/internal/shared/pdf"
)
// 一个本地调试用的小工具:
// 从 JSON 文件(企业报告.json读取 QYGL 聚合结果,使用 gofpdf 生成企业全景报告 PDF输出到当前目录。
func main() {
var (
jsonPath string
outPath string
)
flag.StringVar(&jsonPath, "json", "企业报告.json", "企业报告 JSON 数据源文件路径")
flag.StringVar(&outPath, "out", "企业全景报告_gofpdf.pdf", "输出 PDF 文件路径")
flag.Parse()
logger, _ := zap.NewDevelopment()
defer logger.Sync()
absJSON, _ := filepath.Abs(jsonPath)
fmt.Printf("读取 JSON 数据源:%s\n", absJSON)
data, err := os.ReadFile(jsonPath)
if err != nil {
fmt.Printf("读取 JSON 文件失败: %v\n", err)
os.Exit(1)
}
var report map[string]interface{}
if err := json.Unmarshal(data, &report); err != nil {
fmt.Printf("解析 JSON 失败: %v\n", err)
os.Exit(1)
}
fmt.Println("开始使用 gofpdf 生成企业全景报告 PDF...")
pdfBytes, err := pdf.GenerateQYGLReportPDF(context.Background(), logger, report)
if err != nil {
fmt.Printf("生成 PDF 失败: %v\n", err)
os.Exit(1)
}
if err := os.WriteFile(outPath, pdfBytes, 0644); err != nil {
fmt.Printf("写入 PDF 文件失败: %v\n", err)
os.Exit(1)
}
absOut, _ := filepath.Abs(outPath)
fmt.Printf("PDF 生成完成:%s\n", absOut)
}

View File

@@ -0,0 +1,159 @@
// 仅读取 build 后的报告 JSON本地渲染 qiye.html不执行 BuildReportFromRawSources
//
// go run ./cmd/qygl_report_preview -in resources/dev-report/built.json
// go run ./cmd/qygl_report_preview -in built.json -addr :8899 -watch
//
// 每次打开/刷新页面都会重新读取 -in 文件;加 -watch 后保存 JSON 会自动刷新浏览器。
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"html/template"
"log"
"net/http"
"os"
"path/filepath"
)
func parseBuiltReport(data []byte) (map[string]interface{}, error) {
var root map[string]interface{}
if err := json.Unmarshal(data, &root); err != nil {
return nil, err
}
if _, ok := root["jiguangFull"]; ok {
return nil, fmt.Errorf("检测到 raw 字段 jiguangFull请先执行: go run ./cmd/qygl_report_build -in <raw.json> -out built.json")
}
if k, _ := root["kind"].(string); k == "full" {
r, ok := root["report"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("kind=full 时缺少 report 对象")
}
return r, nil
}
if r, ok := root["report"].(map[string]interface{}); ok {
return r, nil
}
if root["entName"] != nil || root["basic"] != nil || root["reportTime"] != nil {
return root, nil
}
return nil, fmt.Errorf("不是有效的 build 后报告(根级应有 entName、basic、reportTime 之一,或 {\"report\":{...}} / kind=full")
}
func fileVersionTag(path string) (string, error) {
st, err := os.Stat(path)
if err != nil {
return "", err
}
return fmt.Sprintf("%d-%d", st.ModTime().UnixNano(), st.Size()), nil
}
func renderPage(tmpl *template.Template, report map[string]interface{}, injectLive bool) ([]byte, error) {
reportBytes, err := json.Marshal(report)
if err != nil {
return nil, err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, map[string]interface{}{
"ReportJSON": template.JS(reportBytes),
}); err != nil {
return nil, err
}
b := buf.Bytes()
if !injectLive {
return b, nil
}
script := `<script>(function(){var v0=null;function tick(){fetch("/__version?="+Date.now(),{cache:"no-store"}).then(function(r){return r.text();}).then(function(v){if(v==="")return;if(v0===null)v0=v;else if(v0!==v){v0=v;location.reload();}}).catch(function(){});}setInterval(tick,600);tick();})();</script>`
closing := []byte("</body>")
idx := bytes.LastIndex(b, closing)
if idx < 0 {
return append(b, []byte(script)...), nil
}
out := make([]byte, 0, len(b)+len(script))
out = append(out, b[:idx]...)
out = append(out, script...)
out = append(out, b[idx:]...)
return out, nil
}
func main() {
addr := flag.String("addr", ":8899", "监听地址")
root := flag.String("root", ".", "项目根目录(含 resources/qiye.html")
inPath := flag.String("in", "", "build 后的 JSON由 qygl_report_build 生成,或 fixture.full 中的 report 形态)")
watch := flag.Bool("watch", false, "监听 -in 文件变化并自动刷新浏览器(轮询)")
flag.Parse()
if *inPath == "" {
log.Fatal("请指定 -in <built.json>")
}
rootAbs, err := filepath.Abs(*root)
if err != nil {
log.Fatalf("解析 root: %v", err)
}
tplPath := filepath.Join(rootAbs, "resources", "qiye.html")
if _, err := os.Stat(tplPath); err != nil {
log.Fatalf("未找到模板 %s: %v", tplPath, err)
}
var inAbs string
if filepath.IsAbs(*inPath) {
inAbs = *inPath
} else {
inAbs = filepath.Join(rootAbs, *inPath)
}
if _, err := os.Stat(inAbs); err != nil {
log.Fatalf("读取 %s: %v", inAbs, err)
}
tmpl, err := template.ParseFiles(tplPath)
if err != nil {
log.Fatalf("解析模板: %v", err)
}
http.HandleFunc("/__version", func(w http.ResponseWriter, r *http.Request) {
tag, err := fileVersionTag(inAbs)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, _ = w.Write([]byte(tag))
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
raw, err := os.ReadFile(inAbs)
if err != nil {
http.Error(w, "读取报告文件失败: "+err.Error(), http.StatusInternalServerError)
return
}
report, err := parseBuiltReport(raw)
if err != nil {
http.Error(w, "解析 JSON 失败: "+err.Error(), http.StatusInternalServerError)
return
}
html, err := renderPage(tmpl, report, *watch)
if err != nil {
http.Error(w, "渲染失败: "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write(html)
})
log.Printf("报告预览: http://127.0.0.1%s/ (每请求重读 %s", *addr, inAbs)
if *watch {
log.Printf("已启用 -watch保存 JSON 后约 0.6s 内自动刷新页面")
}
if err := http.ListenAndServe(*addr, nil); err != nil {
log.Fatal(err)
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -5,6 +5,8 @@ app:
name: "TYAPI Server" name: "TYAPI Server"
version: "1.0.0" version: "1.0.0"
env: "development" env: "development"
# 子账号入口与主站可同域;邀请链接 {sub_portal_base_url}/sub/auth/register?invite=...
sub_portal_base_url: "http://localhost:5173/"
server: server:
host: "0.0.0.0" host: "0.0.0.0"
@@ -119,6 +121,7 @@ jwt:
api: api:
domain: "api.tianyuanapi.com" domain: "api.tianyuanapi.com"
# public_base_url: "" # 可选,无尾斜杠;空则按 https://{domain} 推导;环境变量 API_PUBLIC_BASE_URL 优先
sms: sms:
access_key_id: "LTAI5tKGB3TVJbMHSoZN3yr9" access_key_id: "LTAI5tKGB3TVJbMHSoZN3yr9"
@@ -129,6 +132,14 @@ sms:
code_length: 6 code_length: 6
expire_time: 5m expire_time: 5m
mock_enabled: false mock_enabled: false
# 签名验证配置(用于防止接口被刷)
signature_enabled: true # 是否启用签名验证
signature_secret: "TyApi2024SMSSecretKey!@#$%^&*()_+QWERTYUIOP" # 签名密钥(请修改为复杂密钥)
# 滑块验证码配置
captcha_enabled: true # 是否启用滑块验证码
captcha_secret: "" # 阿里云验证码密钥加密模式时需要可选EKEY
captcha_endpoint: "captcha.cn-shanghai.aliyuncs.com" # 阿里云验证码服务Endpoint
scene_id: "wynt39to" # 阿里云验证码场景ID
rate_limit: rate_limit:
daily_limit: 10 daily_limit: 10
hourly_limit: 5 hourly_limit: 5
@@ -192,11 +203,12 @@ daily_ratelimit:
- "python" # Python脚本 - "python" # Python脚本
- "java" # Java脚本 - "java" # Java脚本
- "go-http-client" # Go HTTP客户端 - "go-http-client" # Go HTTP客户端
- "LangShen"
enable_referer: true # 是否检查Referer enable_referer: true # 是否检查Referer
allowed_referers: # 允许的Referer allowed_referers: # 允许的Referer
- "https://console.tianyuanapi.com" # 天元API控制台 - "https://console.tianyuanapi.com" # 天元API控制台
- "https://consoletest.tianyuanapi.com" # 天元API测试控制台 - "https://subsole.tianyuanapi.com" # 天元API子账号控制台
enable_proxy_check: false # 是否检查代理 enable_proxy_check: false # 是否检查代理
enable_geo_block: false # 是否启用地理位置阻止 enable_geo_block: false # 是否启用地理位置阻止
@@ -227,13 +239,13 @@ development:
debug: true debug: true
enable_profiler: true enable_profiler: true
enable_cors: true enable_cors: true
cors_allowed_origins: "http://localhost:5173,https://consoletest.tianyuanapi.com,https://console.tianyuanapi.com" cors_allowed_origins: "http://localhost:5173,https://console.tianyuanapi.com,https://subsole.tianyuanapi.com"
cors_allowed_methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS" cors_allowed_methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS"
cors_allowed_headers: "Origin,Content-Type,Accept,Authorization,X-Requested-With,Access-Id" cors_allowed_headers: "Origin,Content-Type,Accept,Authorization,X-Requested-With,Access-Id"
# 企业微信配置 # 企业微信配置
wechat_work: wechat_work:
webhook_url: "" webhook_url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=649bf737-28ca-4f30-ad5f-cfb65b2af113"
secret: "" secret: ""
# =========================================== # ===========================================
@@ -243,7 +255,7 @@ esign:
app_id: "7439073138" app_id: "7439073138"
app_secret: "d76e27fdd169b391e09262a0959dac5c" app_secret: "d76e27fdd169b391e09262a0959dac5c"
server_url: "https://smlopenapi.esign.cn" server_url: "https://smlopenapi.esign.cn"
template_id: "9f7a3f63cc5a48b085b127ba027d234d" template_id: "6c91bfd5b1bb48c585f5eaceeea893d4"
contract: contract:
name: "天远数据API合作协议" name: "天远数据API合作协议"
expire_days: 7 expire_days: 7
@@ -267,6 +279,8 @@ wallet:
default_credit_limit: 50.00 default_credit_limit: 50.00
min_amount: "100.00" # 生产环境最低充值金额 min_amount: "100.00" # 生产环境最低充值金额
max_amount: "100000.00" # 单次最高充值金额 max_amount: "100000.00" # 单次最高充值金额
recharge_bonus_enabled: true # 是否启用充值赠送,设为 false 时仅展示商务洽谈提示
api_store_recharge_tip: "" # 关闭赠送时展示的提示文案,为空则使用默认文案
# 支付宝充值赠送配置 # 支付宝充值赠送配置
alipay_recharge_bonus: alipay_recharge_bonus:
- recharge_amount: 1000.00 # 充值1000元 - recharge_amount: 1000.00 # 充值1000元
@@ -369,7 +383,7 @@ Wxpay:
app_id: "wxa581992dc74d860e" app_id: "wxa581992dc74d860e"
mch_id: "1683589176" mch_id: "1683589176"
mch_certificate_serial_number: "1F4E8B3C39C60035D4CC154F276D03D9CC2C603D" mch_certificate_serial_number: "1F4E8B3C39C60035D4CC154F276D03D9CC2C603D"
mch_apiv3_key: "TY8X9nP2qR5tY7uW3zA6bC4dE1flgGJ0" mch_apiv3_key: "4g9ff87IJMebGmQOG0IshdMbM97YcBnt"
mch_private_key_path: "resources/etc/wxetc_cert/apiclient_key.pem" mch_private_key_path: "resources/etc/wxetc_cert/apiclient_key.pem"
mch_public_key_id: "PUB_KEY_ID_0116835891762025062600211574000800" mch_public_key_id: "PUB_KEY_ID_0116835891762025062600211574000800"
mch_public_key_path: "resources/etc/wxetc_cert/pub_key.pem" mch_public_key_path: "resources/etc/wxetc_cert/pub_key.pem"
@@ -387,6 +401,7 @@ WechatH5:
# =========================================== # ===========================================
# 🔍 天眼查配置 # 🔍 天眼查配置
# =========================================== # ===========================================
tianyancha: tianyancha:
base_url: http://open.api.tianyancha.com/services base_url: http://open.api.tianyancha.com/services
api_key: e6a43dc9-786e-4a16-bb12-392b8201d8e2 api_key: e6a43dc9-786e-4a16-bb12-392b8201d8e2
@@ -530,3 +545,202 @@ jiguang:
max_backups: 5 max_backups: 5
max_age: 30 max_age: 30
compress: true compress: true
# ===========================================
# 📄 PDF生成服务配置
# ===========================================
pdfgen:
# 服务地址配置
development_url: "http://pdfg.tianyuanapi.com" # 开发环境服务地址
production_url: "http://1.117.67.95:15990" # 生产环境服务地址
# API路径配置
api_path: "/api/v1/generate/guangzhou" # PDF生成API路径
# 超时配置
timeout: 120s # 请求超时时间120秒
# 缓存配置
cache:
ttl: 24h # 缓存过期时间24小时
cache_dir: "" # 缓存目录(空则使用默认目录)
max_size: 0 # 最大缓存大小0表示不限制单位字节
# ===========================================
# ✨ 数脉配置走实时接口
# ===========================================
shumai:
url: "https://api.shumaidata.com"
app_id: "pIfqx8MsoTOjhbB762qi5BfkjJ4D7w0O"
app_secret: "BnJWo61hUgNEa5fqBCueiT1IZ1e0DxPU"
# ===========================================
# ✨ 数脉子账号配置走政务
# ===========================================
# 走政务接口使用这个
app_id2: "AwZZRzWkArtFDO2lDcT2jHfuoo9n35Tq"
app_secret2: "nCXN6fKLImjfvzI12hj8O1CMl1gJeaWh"
sign_method: "md5" # 签名方法md5 或 hmac默认 hmac
timeout: 60s # 请求超时时间,默认 60 秒
# 数脉日志配置
logging:
enabled: true
log_dir: "logs/external_services"
service_name: "shumai"
use_daily: true
enable_level_separation: true
# 各级别配置
level_configs:
info:
max_size: 100
max_backups: 5
max_age: 30
compress: true
error:
max_size: 200
max_backups: 10
max_age: 90
compress: true
warn:
max_size: 100
max_backups: 5
max_age: 30
compress: true
# ===========================================
# ✨ 数据宝配置走实时接口
# ===========================================
shujubao:
url: "https://api.chinadatapay.com"
app_secret: "iOk0ALBX0BSdTSTf"
sign_method: "md5" # 签名方法md5 或 hmac默认 hmac
timeout: 60s # 请求超时时间,默认 60 秒
# 数据宝日志配置
logging:
enabled: true
log_dir: "logs/external_services"
service_name: "shujubao"
use_daily: true
enable_level_separation: true
# 各级别配置
level_configs:
info:
max_size: 100
max_backups: 5
max_age: 30
compress: true
error:
max_size: 200
max_backups: 10
max_age: 90
compress: true
warn:
max_size: 100
max_backups: 5
max_age: 30
compress: true
# ===========================================
# ✨ 汇博BHSC配置
# ===========================================
huibo:
url: "http://47.111.187.101:12654/api/v1/project-api/bg_check_ssw"
app_id: "db0029527bb4558c"
app_key: "a6c9935e967894e731c62ecfcd9b7c95"
x_order_code: "cpdd219219725093"
secret_id: "cf581fe84aaf46ca"
aes_key: "NQYN3YO+pb/GEcCBNX0ptMb7cUlnXSPvcX7VvNofBkc="
work_order_code: "gd219219725093"
product_code: "22089"
baseUrl2: "https://napi.zhixin.net:9000/api/data"
app_code2: "1508795945301708800"
logging:
enabled: true
log_dir: "logs/external_services"
service_name: "huibo"
use_daily: true
enable_level_separation: true
level_configs:
info:
max_size: 100
max_backups: 5
max_age: 30
compress: true
error:
max_size: 200
max_backups: 10
max_age: 90
compress: true
warn:
max_size: 100
max_backups: 5
max_age: 30
compress: true
# ===========================================
# 🌐 诺尔智汇配置
# ===========================================
nuoer:
url: "https://api.enolfax.com/enol/api"
app_id: "t4qO2mR3"
app_secret: "d1515bf9ed2f2fe063b5f4f7e2c50f0ec65bfd58"
timeout: 4s
logging:
enabled: true
log_dir: "logs/external_services"
service_name: "nuoer"
use_daily: true
enable_level_separation: true
# 各级别配置
level_configs:
info:
max_size: 100
max_backups: 5
max_age: 30
compress: true
error:
max_size: 200
max_backups: 10
max_age: 90
compress: true
warn:
max_size: 100
max_backups: 5
max_age: 30
compress: true
# ===========================================
# 🌐 海宇API上游数据源配置
# ===========================================
haiyuapi:
base_url: "https://api.haiyudata.com"
access_id: "2c136588d34b47fd" # 请求头 Access-Id
secret_key: "39c5c2c02ff13bf2be0ac0b705e21e28" # Access Key16进制 AES-128 密钥)
timeout: 60s
logging:
enabled: true
log_dir: "logs/external_services"
service_name: "haiyuapi"
use_daily: true
enable_level_separation: true
level_configs:
info:
max_size: 100
max_backups: 5
max_age: 30
compress: true
error:
max_size: 200
max_backups: 10
max_age: 90
compress: true
warn:
max_size: 100
max_backups: 5
max_age: 30
compress: true

View File

@@ -6,6 +6,8 @@
# =========================================== # ===========================================
app: app:
env: development env: development
# 子账号专属前端域名(用于邀请链接复制)
sub_portal_base_url: "http://localhost:5173"
# =========================================== # ===========================================
# 🗄️ 数据库配置 # 🗄️ 数据库配置
@@ -20,6 +22,10 @@ database:
jwt: jwt:
secret: JwT8xR4mN9vP2sL7kH3oB6yC1zA5uF0qE9tW secret: JwT8xR4mN9vP2sL7kH3oB6yC1zA5uF0qE9tW
# 本地联调:企业报告链接与 headless PDF 需能访问到本机服务;端口与 server 监听一致。环境变量 API_PUBLIC_BASE_URL 可覆盖。
api:
public_base_url: "http://127.0.0.1:8080"
# =========================================== # ===========================================
# 📁 存储服务配置 - 七牛云 # 📁 存储服务配置 - 七牛云
# =========================================== # ===========================================
@@ -40,10 +46,10 @@ ocr:
# 📝 e签宝服务配置 # 📝 e签宝服务配置
# =========================================== # ===========================================
esign: esign:
app_id: "7439073713" app_id: "5112008003"
app_secret: "c7d8cb0d701f7890601d221e9b6edfef" app_secret: "d487672273e7aa70c800804a1d9499b9"
server_url: "https://smlopenapi.esign.cn" server_url: "https://openapi.esign.cn"
template_id: "9f7a3f63cc5a48b085b127ba027d234d" template_id: "6c91bfd5b1bb48c585f5eaceeea893d4"
contract: contract:
name: "天远数据API合作协议" name: "天远数据API合作协议"
expire_days: 7 expire_days: 7
@@ -87,7 +93,7 @@ Wxpay:
app_id: "wxa581992dc74d860e" app_id: "wxa581992dc74d860e"
mch_id: "1683589176" mch_id: "1683589176"
mch_certificate_serial_number: "1F4E8B3C39C60035D4CC154F276D03D9CC2C603D" mch_certificate_serial_number: "1F4E8B3C39C60035D4CC154F276D03D9CC2C603D"
mch_apiv3_key: "TY8X9nP2qR5tY7uW3zA6bC4dE1flgGJ0" mch_apiv3_key: "4g9ff87IJMebGmQOG0IshdMbM97YcBnt"
mch_private_key_path: "resources/etc/wxetc_cert/apiclient_key.pem" mch_private_key_path: "resources/etc/wxetc_cert/apiclient_key.pem"
mch_public_key_id: "PUB_KEY_ID_0116835891762025062600211574000800" mch_public_key_id: "PUB_KEY_ID_0116835891762025062600211574000800"
mch_public_key_path: "resources/etc/wxetc_cert/pub_key.pem" mch_public_key_path: "resources/etc/wxetc_cert/pub_key.pem"
@@ -108,6 +114,8 @@ wallet:
default_credit_limit: 0.01 default_credit_limit: 0.01
min_amount: "0.01" # 生产环境最低充值金额 min_amount: "0.01" # 生产环境最低充值金额
max_amount: "100000.00" # 单次最高充值金额 max_amount: "100000.00" # 单次最高充值金额
recharge_bonus_enabled: false # 开发环境可设为 true 测试赠送
api_store_recharge_tip: "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
# 支付宝充值赠送配置 # 支付宝充值赠送配置
alipay_recharge_bonus: alipay_recharge_bonus:
- recharge_amount: 0.01 # 充值1000元 - recharge_amount: 0.01 # 充值1000元
@@ -174,3 +182,13 @@ daily_ratelimit:
enable_user_agent: false # 开发环境禁用User-Agent检查 enable_user_agent: false # 开发环境禁用User-Agent检查
enable_referer: false # 开发环境禁用Referer检查 enable_referer: false # 开发环境禁用Referer检查
enable_proxy_check: false # 开发环境禁用代理检查 enable_proxy_check: false # 开发环境禁用代理检查
# ===========================================
# 📱 短信服务配置
# ===========================================
sms:
# 滑块验证码配置
captcha_enabled: true # 是否启用滑块验证码
captcha_secret: "" # 阿里云验证码密钥(可选)
scene_id: "wynt39to" # 阿里云验证码场景ID

View File

@@ -6,6 +6,8 @@
# =========================================== # ===========================================
app: app:
env: production env: production
# 子账号专属前端域名(用于邀请链接复制)
sub_portal_base_url: "https://subsole.tianyuanapi.com"
# =========================================== # ===========================================
# 🌐 服务器配置 # 🌐 服务器配置
@@ -18,7 +20,7 @@ server:
# =========================================== # ===========================================
development: development:
enable_cors: true enable_cors: true
cors_allowed_origins: "http://localhost:5173,https://consoletest.tianyuanapi.com,https://console.tianyuanapi.com" cors_allowed_origins: "https://console.tianyuanapi.com,https://subsole.tianyuanapi.com"
cors_allowed_methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS" cors_allowed_methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS"
cors_allowed_headers: "Origin,Content-Type,Accept,Authorization,X-Requested-With,Access-Id" cors_allowed_headers: "Origin,Content-Type,Accept,Authorization,X-Requested-With,Access-Id"
@@ -52,6 +54,8 @@ jwt:
api: api:
domain: "api.tianyuanapi.com" domain: "api.tianyuanapi.com"
# 可选:对外可访问的 API 完整基址(无尾斜杠),用于企业报告 reportUrl、PDF 预生成等;不设则按 https://{domain} 推导。环境变量 API_PUBLIC_BASE_URL 优先于本项。
# public_base_url: "https://api.tianyuanapi.com"
# =========================================== # ===========================================
# 📁 存储服务配置 - 七牛云 # 📁 存储服务配置 - 七牛云
# =========================================== # ===========================================
@@ -75,7 +79,7 @@ esign:
app_id: "5112008003" app_id: "5112008003"
app_secret: "d487672273e7aa70c800804a1d9499b9" app_secret: "d487672273e7aa70c800804a1d9499b9"
server_url: "https://openapi.esign.cn" server_url: "https://openapi.esign.cn"
template_id: "9f7a3f63cc5a48b085b127ba027d234d" template_id: "6c91bfd5b1bb48c585f5eaceeea893d4"
contract: contract:
name: "天远数据API合作协议" name: "天远数据API合作协议"
expire_days: 7 expire_days: 7
@@ -109,7 +113,9 @@ wallet:
default_credit_limit: 50.00 default_credit_limit: 50.00
min_amount: "100.00" # 生产环境最低充值金额 min_amount: "100.00" # 生产环境最低充值金额
max_amount: "100000.00" # 单次最高充值金额 max_amount: "100000.00" # 单次最高充值金额
# 支付宝充值赠送配置 recharge_bonus_enabled: false # 暂不赠送,展示商务洽谈提示
api_store_recharge_tip: "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
# 支付宝充值赠送配置recharge_bonus_enabled 为 true 时生效)
alipay_recharge_bonus: alipay_recharge_bonus:
- recharge_amount: 1000.00 # 充值1000元 - recharge_amount: 1000.00 # 充值1000元
bonus_amount: 50.00 # 赠送50元 bonus_amount: 50.00 # 赠送50元
@@ -148,12 +154,22 @@ daily_ratelimit:
- "curl" # 阻止curl请求 - "curl" # 阻止curl请求
- "wget" # 阻止wget请求 - "wget" # 阻止wget请求
- "python-requests" # 阻止Python requests - "python-requests" # 阻止Python requests
- "LangShen" # 阻止LangShen请求
enable_referer: true # 启用Referer检查 enable_referer: true # 启用Referer检查
allowed_referers: # 允许的Referer allowed_referers: # 允许的Referer
- "https://console.tianyuanapi.com" - "https://console.tianyuanapi.com"
- "https://consoletest.tianyuanapi.com" - "https://subsole.tianyuanapi.com"
enable_geo_block: false # 生产环境暂时不启用地理位置阻止 enable_geo_block: false # 生产环境暂时不启用地理位置阻止
enable_proxy_check: true # 启用代理检查 enable_proxy_check: true # 启用代理检查
# ===========================================
# 📱 短信服务配置
# ===========================================
sms:
# 滑块验证码配置
captcha_enabled: true # 是否启用滑块验证码
captcha_secret: "" # 阿里云验证码密钥(可选)
scene_id: "wynt39to" # 阿里云验证码场景ID

View File

@@ -37,3 +37,12 @@ logger:
# =========================================== # ===========================================
jwt: jwt:
secret: test-jwt-secret-key-for-testing-only secret: test-jwt-secret-key-for-testing-only
# ===========================================
# 📱 短信服务配置
# ===========================================
sms:
# 滑块验证码配置
captcha_enabled: true # 是否启用滑块验证码
captcha_secret: "" # 阿里云验证码密钥(可选)
scene_id: "wynt39to" # 阿里云验证码场景ID

View File

@@ -89,7 +89,10 @@ services:
- "25000:8080" - "25000:8080"
volumes: volumes:
- ./logs:/app/logs - ./logs:/app/logs
- ./resources/Pure_Component:/app/resources/Pure_Component # 挂载完整 resources 目录(包含 qiye.html、Pure_Component、pdf 等)
- ./resources:/app/resources
# 持久化PDF缓存目录确保生成的PDF在容器重启后仍然存在
- ./storage/pdfg-cache:/app/storage/pdfg-cache
# user: "1001:1001" # 注释掉使用root权限运行 # user: "1001:1001" # 注释掉使用root权限运行
networks: networks:
- tyapi-network - tyapi-network
@@ -105,14 +108,6 @@ services:
retries: 5 retries: 5
start_period: 60s start_period: 60s
restart: unless-stopped restart: unless-stopped
deploy:
resources:
limits:
memory: 1G
cpus: "1.0"
reservations:
memory: 256M
cpus: "0.3"
# TYAPI Worker 服务 # TYAPI Worker 服务
tyapi-worker: tyapi-worker:
@@ -147,14 +142,6 @@ services:
retries: 5 retries: 5
start_period: 60s start_period: 60s
restart: unless-stopped restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: "0.5"
reservations:
memory: 128M
cpus: "0.1"
# Asynq 任务监控 (生产环境) # Asynq 任务监控 (生产环境)
asynq-monitor: asynq-monitor:

View File

@@ -0,0 +1,347 @@
# 短信接口签名验证使用指南
## 概述
为了防止短信发送接口被恶意刷取系统实现了基于HMAC-SHA256的签名验证机制。所有发送短信的请求必须包含有效的签名否则请求将被拒绝。
## 工作原理
1. **前端生成签名**使用密钥对请求参数进行HMAC-SHA256签名
2. **后端验证签名**:后端使用相同密钥重新计算签名并比对
3. **时间戳验证**防止重放攻击时间戳必须在5分钟内有效
4. **随机数验证**每次请求必须包含唯一的随机字符串nonce
5. **参数编码传输**推荐将所有参数包括签名编码成Base64字符串后传输隐藏参数结构增加安全性
## 配置说明
### 后端配置
`config.yaml` 中配置签名相关参数:
```yaml
sms:
# ... 其他配置 ...
# 签名验证配置
signature_enabled: true # 是否启用签名验证
signature_secret: "TyApi2024SMSSecretKey!@#$%^&*()_+QWERTYUIOP" # 签名密钥(请修改为复杂密钥)
```
**重要提示**
- 生产环境必须修改 `signature_secret` 为复杂的随机字符串
- 密钥长度建议至少32个字符
- 密钥应包含大小写字母、数字和特殊字符
## 签名算法
### 1. 构建待签名字符串
将请求参数(排除`signature`字段按key排序拼接成以下格式
```
key1=value1&key2=value2&timestamp=1234567890&nonce=random_string
```
### 2. 计算HMAC-SHA256签名
使用配置的密钥对待签名字符串进行HMAC-SHA256计算结果转换为hex编码。
### 3. 请求参数
系统支持两种请求方式:
#### 方式1直接传递参数
发送请求时直接传递所有字段:
```json
{
"phone": "13800138000",
"scene": "register",
"timestamp": 1704067200,
"nonce": "a1b2c3d4e5f6g7h8",
"signature": "abc123def456..."
}
```
#### 方式2编码后传输推荐更安全
将所有参数包括签名编码成Base64字符串后传输只传递一个`data`字段:
```json
{
"data": "eyJwaG9uZSI6IjEzODAwMTM4MDAwIiwic2NlbmUiOiJyZWdpc3RlciIsInRpbWVzdGFtcCI6MTcwNDA2NzIwMCwibm9uY2UiOiJhMWIyYzNkNGE1ZjYiLCJzaWduYXR1cmUiOiJhYmMxMjNkZWY0NTYifQ=="
}
```
**编码传输的优势**
- 隐藏参数结构,增加破解难度
- 参数不可见,防止参数被直接修改
- 增加一层编码保护
## 前端实现
### Node.js 示例
参考文件:`tyapi-frontend/public/examples/nodejs/sms_signature_demo.js`
#### 方式1直接传递参数
```javascript
const crypto = require('crypto');
function generateSignature(params, secretKey, timestamp, nonce) {
// 1. 构建待签名字符串
const keys = Object.keys(params)
.filter(k => k !== 'signature')
.sort();
const parts = keys.map(k => `${k}=${params[k]}`);
parts.push(`timestamp=${timestamp}`);
parts.push(`nonce=${nonce}`);
const signString = parts.join('&');
// 2. 计算HMAC-SHA256签名
const signature = crypto
.createHmac('sha256', secretKey)
.update(signString)
.digest('hex');
return signature;
}
// 使用示例
const params = { phone: '13800138000', scene: 'register' };
const timestamp = Math.floor(Date.now() / 1000);
const nonce = generateRandomString(16);
const secretKey = 'your_secret_key';
const signature = generateSignature(params, secretKey, timestamp, nonce);
// 发送请求
const requestBody = {
phone: '13800138000',
scene: 'register',
timestamp: timestamp,
nonce: nonce,
signature: signature,
};
```
#### 方式2编码后传输推荐
```javascript
// 1. 生成签名(同上)
const params = { phone: '13800138000', scene: 'register' };
const timestamp = Math.floor(Date.now() / 1000);
const nonce = generateRandomString(16);
const secretKey = 'your_secret_key';
const signature = generateSignature(params, secretKey, timestamp, nonce);
// 2. 构建包含所有参数的JSON对象
const allParams = {
phone: '13800138000',
scene: 'register',
timestamp: timestamp,
nonce: nonce,
signature: signature,
};
// 3. 编码为Base64
const jsonString = JSON.stringify(allParams);
const encodedData = Buffer.from(jsonString).toString('base64');
// 4. 发送请求只传递data字段
const requestBody = {
data: encodedData,
};
```
### 浏览器 JavaScript 示例
参考文件:`tyapi-frontend/public/examples/javascript/sms_signature_demo.js`
#### 方式1直接传递参数
```javascript
async function generateSignature(params, secretKey, timestamp, nonce) {
// 1. 构建待签名字符串
const keys = Object.keys(params)
.filter(k => k !== 'signature')
.sort();
const parts = keys.map(k => `${k}=${params[k]}`);
parts.push(`timestamp=${timestamp}`);
parts.push(`nonce=${nonce}`);
const signString = parts.join('&');
// 2. 使用Web Crypto API计算HMAC-SHA256签名
const encoder = new TextEncoder();
const keyData = encoder.encode(secretKey);
const messageData = encoder.encode(signString);
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData);
const hashArray = Array.from(new Uint8Array(signature));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}
// 使用示例
const params = { phone: '13800138000', scene: 'register' };
const timestamp = Math.floor(Date.now() / 1000);
const nonce = generateNonce(16);
const secretKey = 'your_secret_key';
const signature = await generateSignature(params, secretKey, timestamp, nonce);
// 发送请求
const requestBody = {
phone: '13800138000',
scene: 'register',
timestamp: timestamp,
nonce: nonce,
signature: signature,
};
```
#### 方式2编码后传输推荐
```javascript
// 1. 生成签名(同上)
const params = { phone: '13800138000', scene: 'register' };
const timestamp = Math.floor(Date.now() / 1000);
const nonce = generateNonce(16);
const secretKey = 'your_secret_key';
const signature = await generateSignature(params, secretKey, timestamp, nonce);
// 2. 构建包含所有参数的JSON对象
const allParams = {
phone: '13800138000',
scene: 'register',
timestamp: timestamp,
nonce: nonce,
signature: signature,
};
// 3. 编码为Base64浏览器环境
const jsonString = JSON.stringify(allParams);
const encodedData = btoa(unescape(encodeURIComponent(jsonString)));
// 4. 发送请求只传递data字段
const requestBody = {
data: encodedData,
};
```
## 密钥隐藏策略
由于前端代码可以被查看,完全隐藏密钥是不可能的。但可以通过以下方式增加破解难度:
### 1. 字符串拆分和拼接
```javascript
function getSecretKey() {
const part1 = 'TyApi2024';
const part2 = 'SMSSecret';
const part3 = 'Key!@#$%^';
return part1 + part2 + part3;
}
```
### 2. 字符数组拼接
```javascript
function getSecretKey() {
const chars = ['T', 'y', 'A', 'p', 'i', ...];
return chars.join('');
}
```
### 3. Base64编码混淆
```javascript
function getSecretKey() {
const encoded = 'base64_encoded_string';
return atob(encoded);
}
```
### 4. 代码混淆
使用构建工具如webpack、rollup等进行代码混淆和压缩使密钥更难被发现。
### 5. 后端代理(推荐)
将签名逻辑放在后端代理接口中,前端只调用代理接口,不直接包含密钥。
## 安全建议
1. **定期更换密钥**建议每3-6个月更换一次签名密钥
2. **监控异常请求**:监控签名验证失败的请求,及时发现攻击行为
3. **结合其他防护措施**
- IP限流
- 设备指纹识别
- 验证码(图形验证码)
- 行为分析
4. **日志记录**记录所有签名验证失败的请求包括IP、User-Agent等信息
## 错误处理
### 常见错误
1. **签名字段缺失**:返回 `"签名字段缺失"`
2. **时间戳无效**:返回 `"时间戳无效"`
3. **请求已过期**:返回 `"请求已过期,时间戳超出容差范围"`
4. **签名验证失败**:返回 `"签名验证失败"`
### 时间戳容差
系统允许的时间戳容差为 **5分钟**300秒。如果请求时间戳与服务器时间差超过5分钟请求将被拒绝。
## 测试
### 测试签名生成
```bash
# 使用Node.js示例
node tyapi-frontend/public/examples/nodejs/sms_signature_demo.js
```
### 测试API调用
```bash
curl -X POST http://localhost:8080/api/v1/users/send-code \
-H "Content-Type: application/json" \
-d '{
"phone": "13800138000",
"scene": "register",
"timestamp": 1704067200,
"nonce": "a1b2c3d4e5f6g7h8",
"signature": "计算得到的签名"
}'
```
## 注意事项
1. **时间同步**:确保客户端和服务器时间同步,避免时间戳验证失败
2. **随机数唯一性**每次请求的nonce应该是唯一的可以使用UUID或时间戳+随机数
3. **密钥安全**:生产环境密钥不要提交到代码仓库,应使用环境变量或密钥管理服务
4. **向后兼容**:如果需要在开发环境禁用签名验证,可以设置 `signature_enabled: false`
## 相关文件
- 后端签名工具:`internal/shared/crypto/signature.go`
- 后端Handler`internal/infrastructure/http/handlers/user_handler.go`
- 配置结构:`internal/config/config.go`
- Node.js示例`tyapi-frontend/public/examples/nodejs/sms_signature_demo.js`
- 浏览器示例:`tyapi-frontend/public/examples/javascript/sms_signature_demo.js`

17
go.mod
View File

@@ -3,7 +3,12 @@ module tyapi-server
go 1.23.4 go 1.23.4
require ( require (
github.com/alibabacloud-go/captcha-20230305 v1.1.3
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13
github.com/alibabacloud-go/tea v1.3.13
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 github.com/aliyun/alibaba-cloud-sdk-go v1.63.107
github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8
github.com/chromedp/chromedp v0.13.2
github.com/gin-contrib/cors v1.7.6 github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.10.1 github.com/gin-gonic/gin v1.10.1
github.com/go-playground/locales v0.14.1 github.com/go-playground/locales v0.14.1
@@ -13,6 +18,7 @@ require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/hibiken/asynq v0.25.1 github.com/hibiken/asynq v0.25.1
github.com/jung-kurt/gofpdf/v2 v2.17.3 github.com/jung-kurt/gofpdf/v2 v2.17.3
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260313013624-04e51e218220
github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_golang v1.22.0
github.com/qiniu/go-sdk/v7 v7.25.4 github.com/qiniu/go-sdk/v7 v7.25.4
github.com/redis/go-redis/v9 v9.11.0 github.com/redis/go-redis/v9 v9.11.0
@@ -46,11 +52,17 @@ require (
github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/debug v1.0.1 // indirect
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect
github.com/aliyun/credentials-go v1.4.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.13.3 // indirect github.com/bytedance/sonic v1.13.3 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chromedp/sysutil v1.1.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
@@ -58,6 +70,7 @@ require (
github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gammazero/toposort v0.1.1 // indirect github.com/gammazero/toposort v0.1.1 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
@@ -65,6 +78,9 @@ require (
github.com/go-openapi/spec v0.20.4 // indirect github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
@@ -104,6 +120,7 @@ require (
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect
github.com/tiendc/go-deepcopy v1.6.0 // indirect github.com/tiendc/go-deepcopy v1.6.0 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect github.com/ugorji/go/codec v1.3.0 // indirect
github.com/xuri/efp v0.0.1 // indirect github.com/xuri/efp v0.0.1 // indirect

205
go.sum
View File

@@ -1,4 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -14,8 +16,54 @@ github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWX
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4= github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4=
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI= github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
github.com/alibabacloud-go/captcha-20230305 v1.1.3 h1:0Aobw12m3x28aeDMPjwjXsfF8MuLvRjlQ4Hhoy5hFOY=
github.com/alibabacloud-go/captcha-20230305 v1.1.3/go.mod h1:ydzBIN2OiM7eeQPpAFyBrv1H5TY1MtUP2rQig44C4UQ=
github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY=
github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE=
github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 h1:Q00FU3H94Ts0ZIHDmY+fYGgB7dV9D/YX6FGsgorQPgw=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE=
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
github.com/alibabacloud-go/tea v1.3.13 h1:WhGy6LIXaMbBM6VBYcsDCz6K/TPsT1Ri2hPmmZffZ94=
github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I=
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0=
github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 h1:qagvUyrgOnBIlVRQWOyCZGVKUIYbMBdGdJ104vBpRFU= github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 h1:qagvUyrgOnBIlVRQWOyCZGVKUIYbMBdGdJ104vBpRFU=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= github.com/aliyun/alibaba-cloud-sdk-go v1.63.107/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
github.com/aliyun/credentials-go v1.4.5 h1:O76WYKgdy1oQYYiJkERjlA2dxGuvLRrzuO2ScrtGWSk=
github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
@@ -29,11 +77,22 @@ github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCN
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8 h1:AqW2bDQf67Zbq6Tpop/+yJSIknxhiQecO2B8jNYTAPs=
github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
github.com/chromedp/chromedp v0.13.2 h1:f6sZFFzCzPLvWSzeuXQBgONKG7zPq54YfEyEj0EplOY=
github.com/chromedp/chromedp v0.13.2/go.mod h1:khsDP9OP20GrowpJfZ7N05iGCwcAYxk7qf9AZBzR3Qw=
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -41,6 +100,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
@@ -59,6 +121,8 @@ github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fq
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 h1:yE7argOs92u+sSCRgqqe6eF+cDaVhSPlioy1UkA0p/w=
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -90,6 +154,12 @@ github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
@@ -98,14 +168,32 @@ github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptG
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
@@ -132,8 +220,10 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/jung-kurt/gofpdf/v2 v2.17.3 h1:otZXZby2gXJ7uU6pzprXHq/R57lsHLi0WtH79VabWxY= github.com/jung-kurt/gofpdf/v2 v2.17.3 h1:otZXZby2gXJ7uU6pzprXHq/R57lsHLi0WtH79VabWxY=
github.com/jung-kurt/gofpdf/v2 v2.17.3/go.mod h1:Qx8ZNg4cNsO5i6uLDiBngnm+ii/FjtAqjRNO6drsoYU= github.com/jung-kurt/gofpdf/v2 v2.17.3/go.mod h1:Qx8ZNg4cNsO5i6uLDiBngnm+ii/FjtAqjRNO6drsoYU=
@@ -154,10 +244,13 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260313013624-04e51e218220 h1:FLQyP/6tTsTEtAhcIq/kS/zkDEMdOMon0I70pXVehOU=
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260313013624-04e51e218220/go.mod h1:+mNMTBuDMdEGhWzoQgc6kBdqeaQpWh5ba8zqmp2MxCU=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
@@ -167,6 +260,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -174,6 +269,7 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -183,6 +279,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
@@ -220,6 +317,9 @@ github.com/smartwalle/ngx v1.0.9 h1:pUXDvWRZJIHVrCKA1uZ15YwNti+5P4GuJGbpJ4WvpMw=
github.com/smartwalle/ngx v1.0.9/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0= github.com/smartwalle/ngx v1.0.9/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E= github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E=
github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc= github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
@@ -231,10 +331,12 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -258,6 +360,9 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tiendc/go-deepcopy v1.6.0 h1:0UtfV/imoCwlLxVsyfUd4hNHnB3drXsfle+wzSCA5Wo= github.com/tiendc/go-deepcopy v1.6.0 h1:0UtfV/imoCwlLxVsyfUd4hNHnB3drXsfle+wzSCA5Wo=
github.com/tiendc/go-deepcopy v1.6.0/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I= github.com/tiendc/go-deepcopy v1.6.0/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
@@ -274,6 +379,8 @@ github.com/xuri/excelize/v2 v2.9.1 h1:VdSGk+rraGmgLHGFaGG9/9IWu1nj4ufjJ7uwMDtj8Q
github.com/xuri/excelize/v2 v2.9.1/go.mod h1:x7L6pKz2dvo9ejrRuD8Lnl98z4JLt0TGAwjhW+EiP8s= github.com/xuri/excelize/v2 v2.9.1/go.mod h1:x7L6pKz2dvo9ejrRuD8Lnl98z4JLt0TGAwjhW+EiP8s=
github.com/xuri/nfp v0.0.1 h1:MDamSGatIvp8uOmDP8FnmjuQpu90NzdJxo7242ANR9Q= github.com/xuri/nfp v0.0.1 h1:MDamSGatIvp8uOmDP8FnmjuQpu90NzdJxo7242ANR9Q=
github.com/xuri/nfp v0.0.1/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/xuri/nfp v0.0.1/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
@@ -309,12 +416,24 @@ golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
@@ -323,26 +442,63 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -350,42 +506,88 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -394,6 +596,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
@@ -412,6 +615,8 @@ gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w= modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w=
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

View File

@@ -26,9 +26,11 @@ import (
articleEntities "tyapi-server/internal/domains/article/entities" articleEntities "tyapi-server/internal/domains/article/entities"
// 统计域实体 // 统计域实体
securityEntities "tyapi-server/internal/domains/security/entities"
statisticsEntities "tyapi-server/internal/domains/statistics/entities" statisticsEntities "tyapi-server/internal/domains/statistics/entities"
apiEntities "tyapi-server/internal/domains/api/entities" apiEntities "tyapi-server/internal/domains/api/entities"
subordinateEntities "tyapi-server/internal/domains/subordinate/entities"
"tyapi-server/internal/infrastructure/database" "tyapi-server/internal/infrastructure/database"
taskEntities "tyapi-server/internal/infrastructure/task/entities" taskEntities "tyapi-server/internal/infrastructure/task/entities"
) )
@@ -256,10 +258,20 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
&statisticsEntities.StatisticsMetric{}, &statisticsEntities.StatisticsMetric{},
&statisticsEntities.StatisticsDashboard{}, &statisticsEntities.StatisticsDashboard{},
&statisticsEntities.StatisticsReport{}, &statisticsEntities.StatisticsReport{},
&securityEntities.SuspiciousIPRecord{},
// api // api
&apiEntities.ApiUser{}, &apiEntities.ApiUser{},
&apiEntities.ApiCall{}, &apiEntities.ApiCall{},
&apiEntities.Report{},
// 下属账号域
&subordinateEntities.SubordinateInvitation{},
&subordinateEntities.UserSubordinateLink{},
&subordinateEntities.SubordinateWalletAllocation{},
&subordinateEntities.SubordinateQuotaPurchase{},
&subordinateEntities.UserProductQuotaAccount{},
&subordinateEntities.UserProductQuotaLedger{},
// 任务域 // 任务域
&taskEntities.AsyncTask{}, &taskEntities.AsyncTask{},

View File

@@ -20,6 +20,8 @@ import (
finance_services "tyapi-server/internal/domains/finance/services" finance_services "tyapi-server/internal/domains/finance/services"
product_entities "tyapi-server/internal/domains/product/entities" product_entities "tyapi-server/internal/domains/product/entities"
product_services "tyapi-server/internal/domains/product/services" product_services "tyapi-server/internal/domains/product/services"
subordinate_entities "tyapi-server/internal/domains/subordinate/entities"
subordinate_repositories "tyapi-server/internal/domains/subordinate/repositories"
user_repositories "tyapi-server/internal/domains/user/repositories" user_repositories "tyapi-server/internal/domains/user/repositories"
task_entities "tyapi-server/internal/infrastructure/task/entities" task_entities "tyapi-server/internal/infrastructure/task/entities"
"tyapi-server/internal/infrastructure/task/interfaces" "tyapi-server/internal/infrastructure/task/interfaces"
@@ -93,6 +95,7 @@ type ApiApplicationServiceImpl struct {
walletService finance_services.WalletAggregateService walletService finance_services.WalletAggregateService
subscriptionService *product_services.ProductSubscriptionService subscriptionService *product_services.ProductSubscriptionService
balanceAlertService finance_services.BalanceAlertService balanceAlertService finance_services.BalanceAlertService
subordinateRepo subordinate_repositories.SubordinateRepository
} }
func NewApiApplicationService( func NewApiApplicationService(
@@ -112,6 +115,7 @@ func NewApiApplicationService(
subscriptionService *product_services.ProductSubscriptionService, subscriptionService *product_services.ProductSubscriptionService,
exportManager *export.ExportManager, exportManager *export.ExportManager,
balanceAlertService finance_services.BalanceAlertService, balanceAlertService finance_services.BalanceAlertService,
subordinateRepo subordinate_repositories.SubordinateRepository,
) ApiApplicationService { ) ApiApplicationService {
service := &ApiApplicationServiceImpl{ service := &ApiApplicationServiceImpl{
apiCallService: apiCallService, apiCallService: apiCallService,
@@ -130,6 +134,7 @@ func NewApiApplicationService(
walletService: walletService, walletService: walletService,
subscriptionService: subscriptionService, subscriptionService: subscriptionService,
balanceAlertService: balanceAlertService, balanceAlertService: balanceAlertService,
subordinateRepo: subordinateRepo,
} }
return service return service
@@ -226,13 +231,19 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
// 4. 验证IP白名单非开发环境 // 4. 验证IP白名单非开发环境
if !s.config.App.IsDevelopment() && !cmd.Options.IsDebug { if !s.config.App.IsDevelopment() && !cmd.Options.IsDebug {
whiteListIPs := make([]string, 0, len(apiUser.WhiteList))
for _, item := range apiUser.WhiteList {
whiteListIPs = append(whiteListIPs, item.IPAddress)
}
// 添加调试日志 // 添加调试日志
s.logger.Info("开始验证白名单", s.logger.Info("开始验证白名单",
zap.String("userId", apiUser.UserId), zap.String("userId", apiUser.UserId),
zap.String("clientIP", cmd.ClientIP), zap.String("clientIP", cmd.ClientIP),
zap.Bool("isDevelopment", s.config.App.IsDevelopment()), zap.Bool("isDevelopment", s.config.App.IsDevelopment()),
zap.Bool("isDebug", cmd.Options.IsDebug), zap.Bool("isDebug", cmd.Options.IsDebug),
zap.Int("whiteListCount", len(apiUser.WhiteList))) zap.Int("whiteListCount", len(apiUser.WhiteList)),
zap.Strings("whiteListIPs", whiteListIPs))
// 输出白名单详细信息(用于调试) // 输出白名单详细信息(用于调试)
for idx, item := range apiUser.WhiteList { for idx, item := range apiUser.WhiteList {
@@ -246,24 +257,27 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
s.logger.Error("IP不在白名单内", s.logger.Error("IP不在白名单内",
zap.String("userId", apiUser.UserId), zap.String("userId", apiUser.UserId),
zap.String("ip", cmd.ClientIP), zap.String("ip", cmd.ClientIP),
zap.Int("whiteListSize", len(apiUser.WhiteList))) zap.Int("whiteListSize", len(apiUser.WhiteList)),
zap.Strings("whiteListIPs", whiteListIPs))
return nil, ErrInvalidIP return nil, ErrInvalidIP
} }
s.logger.Info("白名单验证通过", zap.String("ip", cmd.ClientIP)) s.logger.Info("白名单验证通过",
zap.String("ip", cmd.ClientIP),
zap.Strings("whiteListIPs", whiteListIPs))
} }
// 5. 验证钱包状态 // 5. 验证订阅(与扣费金额一致,便于余额预检使用订阅价)
if err := s.validateWalletStatus(ctx, apiUser.UserId, product); err != nil {
return nil, err
}
// 6. 验证订阅状态并获取订阅信息
subscription, err := s.validateSubscriptionStatus(ctx, apiUser.UserId, product) subscription, err := s.validateSubscriptionStatus(ctx, apiUser.UserId, product)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result.SetSubscription(subscription) result.SetSubscription(subscription)
// 6. 验证钱包状态(有订阅时按订阅价与目录价取较大者预检,避免代配价高于目录价时误判余额不足)
if err := s.validateWalletStatus(ctx, apiUser.UserId, product, subscription); err != nil {
return nil, err
}
// 7. 解密参数 // 7. 解密参数
requestParams, err := crypto.AesDecrypt(cmd.Data, apiUser.SecretKey) requestParams, err := crypto.AesDecrypt(cmd.Data, apiUser.SecretKey)
if err != nil { if err != nil {
@@ -277,6 +291,44 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
s.logger.Error("解析解密参数失败", zap.Error(err)) s.logger.Error("解析解密参数失败", zap.Error(err))
return nil, ErrDecryptFail return nil, ErrDecryptFail
} }
// 7.1 子账号主账号 AccessId 校验(仅在请求参数中携带时生效)
if parentAccessID, ok := extractParentAccessID(paramsMap); ok {
if s.subordinateRepo == nil {
s.logger.Error("子账号主账号AccessId校验失败subordinateRepo未初始化")
return nil, ErrSystem
}
link, err := s.subordinateRepo.FindLinkByChildUserID(ctx, apiUser.UserId)
if err != nil {
s.logger.Error("查询子账号主从关系失败",
zap.String("user_id", apiUser.UserId),
zap.Error(err))
return nil, ErrSystem
}
if link == nil {
s.logger.Warn("子账号主账号AccessId校验失败未找到主从关系",
zap.String("user_id", apiUser.UserId),
zap.String("parent_access_id", parentAccessID))
return nil, ErrSubordinateLinkNotFound
}
parentApiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, link.ParentUserID)
if err != nil {
s.logger.Error("加载主账号API用户失败",
zap.String("child_user_id", apiUser.UserId),
zap.String("parent_user_id", link.ParentUserID),
zap.Error(err))
return nil, ErrSystem
}
if parentApiUser == nil || parentApiUser.AccessId != parentAccessID {
s.logger.Warn("子账号主账号AccessId校验失败主账号不匹配",
zap.String("child_user_id", apiUser.UserId),
zap.String("parent_user_id", link.ParentUserID),
zap.String("parent_access_id", parentAccessID))
return nil, ErrSubordinateParentMismatch
}
}
result.SetRequestParams(paramsMap) result.SetRequestParams(paramsMap)
// 8. 获取合同信息 // 8. 获取合同信息
@@ -293,6 +345,26 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
return result, nil return result, nil
} }
// extractParentAccessID 从解密参数中提取主账号 AccessId
// 仅支持键名master_accessid
func extractParentAccessID(params map[string]interface{}) (string, bool) {
if len(params) == 0 {
return "", false
}
value, ok := params["master_accessid"]
if !ok {
return "", false
}
if str, ok := value.(string); ok {
str = strings.TrimSpace(str)
if str != "" {
return str, true
}
}
return "", false
}
// callExternalApi 同步调用外部API // callExternalApi 同步调用外部API
func (s *ApiApplicationServiceImpl) callExternalApi(ctx context.Context, cmd *commands.ApiCallCommand, validation *dto.ApiCallValidationResult) (string, error) { func (s *ApiApplicationServiceImpl) callExternalApi(ctx context.Context, cmd *commands.ApiCallCommand, validation *dto.ApiCallValidationResult) (string, error) {
// 创建CallContext // 创建CallContext
@@ -319,15 +391,28 @@ func (s *ApiApplicationServiceImpl) callExternalApi(ctx context.Context, cmd *co
callContext) callContext)
if err != nil { if err != nil {
mappedErrorType := entities.ApiCallErrorSystem
if errors.Is(err, processors.ErrDatasource) { if errors.Is(err, processors.ErrDatasource) {
return "", ErrSystem mappedErrorType = entities.ApiCallErrorDatasource
} else if errors.Is(err, processors.ErrInvalidParam) { } else if errors.Is(err, processors.ErrInvalidParam) {
return "", ErrInvalidParam mappedErrorType = entities.ApiCallErrorInvalidParam
} else if errors.Is(err, processors.ErrNotFound) { } else if errors.Is(err, processors.ErrNotFound) {
return "", ErrQueryEmpty mappedErrorType = entities.ApiCallErrorQueryEmpty
} else {
return "", ErrSystem
} }
s.logger.Error("调用第三方接口失败",
zap.String("transaction_id", validation.ApiCall.TransactionId),
zap.String("api_name", cmd.ApiName),
zap.String("error_type", mappedErrorType),
zap.Error(err))
if mappedErrorType == entities.ApiCallErrorInvalidParam {
return "", ErrInvalidParam
}
if mappedErrorType == entities.ApiCallErrorQueryEmpty {
return "", ErrQueryEmpty
}
return "", ErrSystem
} }
return string(response), nil return string(response), nil
@@ -609,42 +694,7 @@ func (s *ApiApplicationServiceImpl) GetUserApiCalls(ctx context.Context, userID
// 转换为响应DTO // 转换为响应DTO
var items []dto.ApiCallRecordResponse var items []dto.ApiCallRecordResponse
for _, call := range calls { for _, call := range calls {
// 解密请求参数 requestParamsStr := call.RequestParams
var requestParamsStr string = call.RequestParams // 默认使用原始值
if call.UserId != nil && *call.UserId != "" {
// 获取用户的API密钥信息
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, *call.UserId)
if err != nil {
s.logger.Error("获取用户API信息失败",
zap.Error(err),
zap.String("call_id", call.ID),
zap.String("user_id", *call.UserId))
// 获取失败时使用原始值
} else if apiUser.SecretKey != "" {
// 使用用户的SecretKey解密请求参数
decryptedParams, err := s.DecryptParams(ctx, *call.UserId, &commands.DecryptCommand{
EncryptedData: call.RequestParams,
SecretKey: apiUser.SecretKey,
})
if err != nil {
s.logger.Error("解密请求参数失败",
zap.Error(err),
zap.String("call_id", call.ID),
zap.String("user_id", *call.UserId))
// 解密失败时使用原始值
} else {
// 将解密后的数据转换为JSON字符串
if jsonBytes, err := json.Marshal(decryptedParams); err == nil {
requestParamsStr = string(jsonBytes)
} else {
s.logger.Error("序列化解密参数失败",
zap.Error(err),
zap.String("call_id", call.ID))
// 序列化失败时使用原始值
}
}
}
}
item := dto.ApiCallRecordResponse{ item := dto.ApiCallRecordResponse{
ID: call.ID, ID: call.ID,
@@ -713,50 +763,50 @@ func (s *ApiApplicationServiceImpl) GetAdminApiCalls(ctx context.Context, filter
continue continue
} }
// 解密请求参数 // // 解密请求参数
var requestParamsStr string = call.RequestParams // 默认使用原始值 // var requestParamsStr string = call.RequestParams // 默认使用原始值
if call.UserId != nil && *call.UserId != "" { // if call.UserId != nil && *call.UserId != "" {
// 获取用户的API密钥信息 // // 获取用户的API密钥信息
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, *call.UserId) // apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, *call.UserId)
if err != nil { // if err != nil {
s.logger.Error("获取用户API信息失败", // s.logger.Error("获取用户API信息失败",
zap.Error(err), // zap.Error(err),
zap.String("call_id", call.ID), // zap.String("call_id", call.ID),
zap.String("user_id", *call.UserId)) // zap.String("user_id", *call.UserId))
// 获取失败时使用原始值 // // 获取失败时使用原始值
} else if apiUser.SecretKey != "" { // } else if apiUser.SecretKey != "" {
// 使用用户的SecretKey解密请求参数 // // 使用用户的SecretKey解密请求参数
decryptedParams, err := s.DecryptParams(ctx, *call.UserId, &commands.DecryptCommand{ // decryptedParams, err := s.DecryptParams(ctx, *call.UserId, &commands.DecryptCommand{
EncryptedData: call.RequestParams, // EncryptedData: call.RequestParams,
SecretKey: apiUser.SecretKey, // SecretKey: apiUser.SecretKey,
}) // })
if err != nil { // if err != nil {
s.logger.Error("解密请求参数失败", // s.logger.Error("解密请求参数失败",
zap.Error(err), // zap.Error(err),
zap.String("call_id", call.ID), // zap.String("call_id", call.ID),
zap.String("user_id", *call.UserId)) // zap.String("user_id", *call.UserId))
// 解密失败时使用原始值 // // 解密失败时使用原始值
} else { // } else {
// 将解密后的数据转换为JSON字符串 // // 将解密后的数据转换为JSON字符串
if jsonBytes, err := json.Marshal(decryptedParams); err == nil { // if jsonBytes, err := json.Marshal(decryptedParams); err == nil {
requestParamsStr = string(jsonBytes) // requestParamsStr = string(jsonBytes)
} else { // } else {
s.logger.Error("序列化解密参数失败", // s.logger.Error("序列化解密参数失败",
zap.Error(err), // zap.Error(err),
zap.String("call_id", call.ID)) // zap.String("call_id", call.ID))
// 序列化失败时使用原始值 // // 序列化失败时使用原始值
} // }
} // }
} // }
} // }
item := dto.ApiCallRecordResponse{ item := dto.ApiCallRecordResponse{
ID: call.ID, ID: call.ID,
AccessId: call.AccessId, AccessId: call.AccessId,
TransactionId: call.TransactionId, TransactionId: call.TransactionId,
ClientIp: call.ClientIp, ClientIp: call.ClientIp,
RequestParams: requestParamsStr, // RequestParams: requestParamsStr,
Status: call.Status, Status: call.Status,
} }
// 安全设置用户ID // 安全设置用户ID
@@ -1121,6 +1171,24 @@ func (s *ApiApplicationServiceImpl) ProcessDeduction(ctx context.Context, cmd *c
return err return err
} }
// 优先扣减产品额度(若存在且可用),避免子账号有额度却因钱包余额不足失败
deductedByQuota, err := s.tryDeductQuota(ctx, cmd.UserID, cmd.ProductID, cmd.ApiCallID, cmd.TransactionID)
if err != nil {
s.logger.Error("额度扣减失败",
zap.String("transaction_id", cmd.TransactionID),
zap.String("user_id", cmd.UserID),
zap.String("product_id", cmd.ProductID),
zap.Error(err))
return err
}
if deductedByQuota {
s.logger.Info("额度扣减成功",
zap.String("transaction_id", cmd.TransactionID),
zap.String("user_id", cmd.UserID),
zap.String("product_id", cmd.ProductID))
return nil
}
if err := s.walletService.Deduct(ctx, cmd.UserID, amount, cmd.ApiCallID, cmd.TransactionID, cmd.ProductID); err != nil { if err := s.walletService.Deduct(ctx, cmd.UserID, amount, cmd.ApiCallID, cmd.TransactionID, cmd.ProductID); err != nil {
s.logger.Error("扣款处理失败", s.logger.Error("扣款处理失败",
zap.String("transaction_id", cmd.TransactionID), zap.String("transaction_id", cmd.TransactionID),
@@ -1214,7 +1282,26 @@ func (s *ApiApplicationServiceImpl) ProcessCompensation(ctx context.Context, cmd
} }
// validateWalletStatus 验证钱包状态 // validateWalletStatus 验证钱包状态
func (s *ApiApplicationServiceImpl) validateWalletStatus(ctx context.Context, userID string, product *product_entities.Product) error { func (s *ApiApplicationServiceImpl) validateWalletStatus(ctx context.Context, userID string, product *product_entities.Product, subscription *product_entities.Subscription) error {
// 若用户在该产品有可用额度,则本次调用将走额度扣减,不再要求钱包余额预检通过
if s.subordinateRepo != nil {
quotaAccount, err := s.subordinateRepo.FindQuotaAccount(ctx, userID, product.ID)
if err != nil {
s.logger.Error("查询额度账户失败",
zap.String("user_id", userID),
zap.String("product_id", product.ID),
zap.Error(err))
return ErrSystem
}
if quotaAccount != nil && quotaAccount.AvailableQuota > 0 {
s.logger.Info("额度校验通过,跳过钱包余额预检",
zap.String("user_id", userID),
zap.String("product_id", product.ID),
zap.Int64("available_quota", quotaAccount.AvailableQuota))
return nil
}
}
// 1. 获取用户钱包信息 // 1. 获取用户钱包信息
wallet, err := s.walletService.LoadWalletByUserId(ctx, userID) wallet, err := s.walletService.LoadWalletByUserId(ctx, userID)
if err != nil { if err != nil {
@@ -1232,8 +1319,13 @@ func (s *ApiApplicationServiceImpl) validateWalletStatus(ctx context.Context, us
return ErrFrozenAccount return ErrFrozenAccount
} }
// 3. 检查钱包余额是否充足 // 3. 检查钱包余额是否充足(有订阅时与扣费金额对齐:取目录价与订阅价较大者)
requiredAmount := product.Price requiredAmount := product.Price
if subscription != nil {
if subscription.Price.GreaterThan(requiredAmount) {
requiredAmount = subscription.Price
}
}
if wallet.Balance.LessThan(requiredAmount) { if wallet.Balance.LessThan(requiredAmount) {
s.logger.Error("钱包余额不足", s.logger.Error("钱包余额不足",
zap.String("user_id", userID), zap.String("user_id", userID),
@@ -1259,6 +1351,56 @@ func (s *ApiApplicationServiceImpl) validateWalletStatus(ctx context.Context, us
return nil return nil
} }
// tryDeductQuota 尝试扣减产品额度;若不存在额度账户则返回 false,nil 以便回退钱包扣款
func (s *ApiApplicationServiceImpl) tryDeductQuota(ctx context.Context, userID, productID, apiCallID, transactionID string) (bool, error) {
if s.subordinateRepo == nil || productID == "" {
return false, nil
}
var deducted bool
err := s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
account, err := s.subordinateRepo.FindQuotaAccount(txCtx, userID, productID)
if err != nil {
return err
}
if account == nil {
return nil
}
if account.AvailableQuota <= 0 {
return ErrInsufficientBalance
}
before := account.AvailableQuota
account.AvailableQuota -= 1
account.UsedQuota += 1
if err := s.subordinateRepo.UpdateQuotaAccount(txCtx, account); err != nil {
return err
}
ledger := &subordinate_entities.UserProductQuotaLedger{
UserID: userID,
ProductID: productID,
ChangeType: subordinate_entities.QuotaLedgerChangeTypeConsumeAPI,
DeltaQuota: -1,
BeforeQuota: before,
AfterQuota: account.AvailableQuota,
SourceID: apiCallID,
OperatorID: userID,
Remark: fmt.Sprintf("API调用扣减transaction_id=%s", transactionID),
}
if err := s.subordinateRepo.CreateQuotaLedger(txCtx, ledger); err != nil {
return err
}
deducted = true
return nil
})
if err != nil {
return false, err
}
return deducted, nil
}
// validateSubscriptionStatus 验证订阅状态并返回订阅信息 // validateSubscriptionStatus 验证订阅状态并返回订阅信息
func (s *ApiApplicationServiceImpl) validateSubscriptionStatus(ctx context.Context, userID string, product *product_entities.Product) (*product_entities.Subscription, error) { func (s *ApiApplicationServiceImpl) validateSubscriptionStatus(ctx context.Context, userID string, product *product_entities.Product) (*product_entities.Subscription, error) {
// 1. 检查用户是否已订阅该产品 // 1. 检查用户是否已订阅该产品

View File

@@ -16,13 +16,13 @@ type ApiCallValidationResult struct {
SecretKey string `json:"secret_key"` SecretKey string `json:"secret_key"`
IsValid bool `json:"is_valid"` IsValid bool `json:"is_valid"`
ErrorMessage string `json:"error_message"` ErrorMessage string `json:"error_message"`
// 新增字段 // 新增字段
ContractCode string `json:"contract_code"` ContractCode string `json:"contract_code"`
ApiCall *api_entities.ApiCall `json:"api_call"` ApiCall *api_entities.ApiCall `json:"api_call"`
RequestParams map[string]interface{} `json:"request_params"` RequestParams map[string]interface{} `json:"request_params"`
Product *product_entities.Product `json:"product"` Product *product_entities.Product `json:"product"`
Subscription *product_entities.Subscription `json:"subscription"` Subscription *product_entities.Subscription `json:"subscription"`
} }
// GetUserID 获取用户ID // GetUserID 获取用户ID
@@ -99,6 +99,6 @@ func (r *ApiCallValidationResult) SetContractCode(code string) {
// SetSubscription 设置订阅信息(包含实际扣费金额) // SetSubscription 设置订阅信息(包含实际扣费金额)
func (r *ApiCallValidationResult) SetSubscription(subscription *product_entities.Subscription) { func (r *ApiCallValidationResult) SetSubscription(subscription *product_entities.Subscription) {
r.SubscriptionID = subscription.ID r.SubscriptionID = subscription.ID
r.Amount = subscription.Price // 使用订阅价格作为扣费金额 r.Amount = subscription.Price // 使用订阅价格作为扣费金额
r.Subscription = subscription r.Subscription = subscription
} }

View File

@@ -5,6 +5,7 @@ import "errors"
// API调用相关错误类型 // API调用相关错误类型
var ( var (
ErrQueryEmpty = errors.New("查询为空") ErrQueryEmpty = errors.New("查询为空")
ErrQueryFailed = errors.New("查询失败")
ErrSystem = errors.New("接口异常") ErrSystem = errors.New("接口异常")
ErrDecryptFail = errors.New("解密失败") ErrDecryptFail = errors.New("解密失败")
ErrRequestParam = errors.New("请求参数结构不正确") ErrRequestParam = errors.New("请求参数结构不正确")
@@ -21,12 +22,15 @@ var (
ErrProductNotSubscribed = errors.New("未订阅此产品") ErrProductNotSubscribed = errors.New("未订阅此产品")
ErrSubscriptionExpired = errors.New("订阅已过期") ErrSubscriptionExpired = errors.New("订阅已过期")
ErrSubscriptionSuspended = errors.New("订阅已暂停") ErrSubscriptionSuspended = errors.New("订阅已暂停")
ErrBusiness = errors.New("业务失败") ErrBusiness = errors.New("业务失败")
ErrSubordinateLinkNotFound = errors.New("非子账号无法使用master_accessid")
ErrSubordinateParentMismatch = errors.New("master_accessid与主账号不匹配")
) )
// 错误码映射 - 严格按照用户要求 // 错误码映射 - 严格按照用户要求
var ErrorCodeMap = map[error]int{ var ErrorCodeMap = map[error]int{
ErrQueryEmpty: 1000, ErrQueryEmpty: 1000,
ErrQueryFailed: 1000,
ErrSystem: 1001, ErrSystem: 1001,
ErrDecryptFail: 1002, ErrDecryptFail: 1002,
ErrRequestParam: 1003, ErrRequestParam: 1003,
@@ -43,7 +47,9 @@ var ErrorCodeMap = map[error]int{
ErrProductNotSubscribed: 1008, ErrProductNotSubscribed: 1008,
ErrSubscriptionExpired: 1008, ErrSubscriptionExpired: 1008,
ErrSubscriptionSuspended: 1008, ErrSubscriptionSuspended: 1008,
ErrBusiness: 2001, ErrBusiness: 2001,
ErrSubordinateLinkNotFound: 1301,
ErrSubordinateParentMismatch: 1302,
} }
// GetErrorCode 获取错误对应的错误码 // GetErrorCode 获取错误对应的错误码

View File

@@ -32,6 +32,22 @@ type CertificationApplicationService interface {
// 获取认证列表(管理员) // 获取认证列表(管理员)
ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) (*responses.CertificationListResponse, error) ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) (*responses.CertificationListResponse, error)
// ================ 管理员后台操作用例 ================
// AdminCompleteCertificationWithoutContract 管理员代用户完成认证(暂不关联合同)
AdminCompleteCertificationWithoutContract(ctx context.Context, cmd *commands.AdminCompleteCertificationCommand) (*responses.CertificationResponse, error)
// AdminListSubmitRecords 管理端分页查询企业信息提交记录
AdminListSubmitRecords(ctx context.Context, query *queries.AdminListSubmitRecordsQuery) (*responses.AdminSubmitRecordsListResponse, error)
// AdminGetSubmitRecordByID 管理端获取单条提交记录详情
AdminGetSubmitRecordByID(ctx context.Context, recordID string) (*responses.AdminSubmitRecordDetail, error)
// AdminApproveSubmitRecord 管理端审核通过(按提交记录 ID
AdminApproveSubmitRecord(ctx context.Context, recordID, adminID, remark string) error
// AdminRejectSubmitRecord 管理端审核拒绝(按提交记录 ID
AdminRejectSubmitRecord(ctx context.Context, recordID, adminID, remark string) error
// AdminTransitionCertificationStatus 管理端按用户变更认证状态以状态机为准info_submitted=通过 / info_rejected=拒绝)
AdminTransitionCertificationStatus(ctx context.Context, cmd *commands.AdminTransitionCertificationStatusCommand) error
// ================ e签宝回调处理 ================ // ================ e签宝回调处理 ================
// 处理e签宝回调 // 处理e签宝回调

View File

@@ -71,7 +71,20 @@ type EsignOrganization struct {
// 可以根据需要添加更多企业信息字段 // 可以根据需要添加更多企业信息字段
} }
// AdminCompleteCertificationCommand 管理员代用户完成认证命令(可不关联合同)
type AdminCompleteCertificationCommand struct {
// AdminID 从JWT中获取不从请求体传递因此不做必填校验
AdminID string `json:"-"`
UserID string `json:"user_id" validate:"required"`
CompanyName string `json:"company_name" validate:"required,min=2,max=100"`
UnifiedSocialCode string `json:"unified_social_code" validate:"required"`
LegalPersonName string `json:"legal_person_name" validate:"required,min=2,max=20"`
LegalPersonID string `json:"legal_person_id" validate:"required"`
LegalPersonPhone string `json:"legal_person_phone" validate:"required"`
EnterpriseAddress string `json:"enterprise_address" validate:"required"`
// 备注信息,用于记录后台操作原因
Reason string `json:"reason" validate:"required"`
}
// ForceTransitionStatusCommand 强制状态转换命令(管理员) // ForceTransitionStatusCommand 强制状态转换命令(管理员)
type ForceTransitionStatusCommand struct { type ForceTransitionStatusCommand struct {
CertificationID string `json:"certification_id" validate:"required"` CertificationID string `json:"certification_id" validate:"required"`
@@ -81,6 +94,14 @@ type ForceTransitionStatusCommand struct {
Force bool `json:"force,omitempty"` // 是否强制执行,跳过业务规则验证 Force bool `json:"force,omitempty"` // 是否强制执行,跳过业务规则验证
} }
// AdminTransitionCertificationStatusCommand 管理端变更认证状态(以状态机为准,用于审核通过/拒绝等)
type AdminTransitionCertificationStatusCommand struct {
AdminID string `json:"-"`
UserID string `json:"user_id" validate:"required"`
TargetStatus string `json:"target_status" validate:"required,oneof=info_submitted info_rejected"` // 审核通过 -> info_submitted审核拒绝 -> info_rejected
Remark string `json:"remark"`
}
// SubmitEnterpriseInfoCommand 提交企业信息命令 // SubmitEnterpriseInfoCommand 提交企业信息命令
type SubmitEnterpriseInfoCommand struct { type SubmitEnterpriseInfoCommand struct {
UserID string `json:"-" comment:"用户唯一标识从JWT token获取不在JSON中暴露"` UserID string `json:"-" comment:"用户唯一标识从JWT token获取不在JSON中暴露"`
@@ -91,4 +112,19 @@ type SubmitEnterpriseInfoCommand struct {
LegalPersonPhone string `json:"legal_person_phone" binding:"required,phone" comment:"法定代表人手机号11位13800138000"` LegalPersonPhone string `json:"legal_person_phone" binding:"required,phone" comment:"法定代表人手机号11位13800138000"`
EnterpriseAddress string `json:"enterprise_address" binding:"required,enterprise_address" comment:"企业地址,如:北京市海淀区"` EnterpriseAddress string `json:"enterprise_address" binding:"required,enterprise_address" comment:"企业地址,如:北京市海淀区"`
VerificationCode string `json:"verification_code" binding:"required,len=6" comment:"验证码"` VerificationCode string `json:"verification_code" binding:"required,len=6" comment:"验证码"`
// 营业执照图片 URL单张
BusinessLicenseImageURL string `json:"business_license_image_url" binding:"omitempty,url" comment:"营业执照图片URL"`
// 办公场地图片 URL 列表(前端传 string 数组)
OfficePlaceImageURLs []string `json:"office_place_image_urls" binding:"omitempty,dive,url" comment:"办公场地图片URL列表"`
// 授权代表信息(与前端 authorized_rep_* 及表字段一致)
AuthorizedRepName string `json:"authorized_rep_name" binding:"omitempty,min=2,max=20" comment:"授权代表姓名"`
AuthorizedRepID string `json:"authorized_rep_id" binding:"omitempty,id_card" comment:"授权代表身份证号"`
AuthorizedRepPhone string `json:"authorized_rep_phone" binding:"omitempty,phone" comment:"授权代表手机号"`
AuthorizedRepIDImageURLs []string `json:"authorized_rep_id_image_urls" binding:"omitempty,dive,url" comment:"授权代表身份证正反面图片URL"`
// 应用场景
APIUsage string `json:"api_usage" binding:"omitempty,min=5,max=500" comment:"接口用途及业务场景说明"`
ScenarioAttachmentURLs []string `json:"scenario_attachment_urls" binding:"omitempty,dive,url" comment:"场景附件图片URL列表"`
} }

View File

@@ -192,3 +192,13 @@ func (q *GetSystemMonitoringQuery) ShouldIncludeMetric(metric string) bool {
} }
return false return false
} }
// AdminListSubmitRecordsQuery 管理端企业信息提交记录列表查询(以状态机 certification_status 为准,不做审核状态筛选)
type AdminListSubmitRecordsQuery struct {
Page int `json:"page" form:"page"`
PageSize int `json:"page_size" form:"page_size"`
CertificationStatus string `json:"certification_status" form:"certification_status"` // 按认证状态筛选,如 info_pending_review / info_submitted / info_rejected空为全部
CompanyName string `json:"company_name" form:"company_name"` // 企业名称(模糊搜索)
LegalPersonPhone string `json:"legal_person_phone" form:"legal_person_phone"` // 法人手机号
LegalPersonName string `json:"legal_person_name" form:"legal_person_name"` // 法人姓名(模糊搜索)
}

View File

@@ -53,13 +53,13 @@ type CertificationResponse struct {
// ConfirmAuthResponse 确认认证状态响应 // ConfirmAuthResponse 确认认证状态响应
type ConfirmAuthResponse struct { type ConfirmAuthResponse struct {
Status enums.CertificationStatus `json:"status"` Status enums.CertificationStatus `json:"status"`
Reason string `json:"reason"` Reason string `json:"reason"`
} }
// ConfirmSignResponse 确认签署状态响应 // ConfirmSignResponse 确认签署状态响应
type ConfirmSignResponse struct { type ConfirmSignResponse struct {
Status enums.CertificationStatus `json:"status"` Status enums.CertificationStatus `json:"status"`
Reason string `json:"reason"` Reason string `json:"reason"`
} }
// CertificationListResponse 认证列表响应 // CertificationListResponse 认证列表响应
@@ -81,7 +81,6 @@ type ContractSignUrlResponse struct {
Message string `json:"message"` Message string `json:"message"`
} }
// SystemMonitoringResponse 系统监控响应 // SystemMonitoringResponse 系统监控响应
type SystemMonitoringResponse struct { type SystemMonitoringResponse struct {
TimeRange string `json:"time_range"` TimeRange string `json:"time_range"`
@@ -111,6 +110,55 @@ type SystemHealthStatus struct {
Details map[string]interface{} `json:"details,omitempty"` Details map[string]interface{} `json:"details,omitempty"`
} }
// AdminSubmitRecordItem 管理端提交记录列表项
type AdminSubmitRecordItem struct {
ID string `json:"id"`
UserID string `json:"user_id"`
CompanyName string `json:"company_name"`
UnifiedSocialCode string `json:"unified_social_code"`
LegalPersonName string `json:"legal_person_name"`
SubmitAt time.Time `json:"submit_at"`
Status string `json:"status"`
CertificationStatus string `json:"certification_status,omitempty"` // 以状态机为准info_pending_review/info_submitted/info_rejected 等
}
// AdminSubmitRecordDetail 管理端提交记录详情(含完整信息与图片 URL
type AdminSubmitRecordDetail struct {
ID string `json:"id"`
UserID string `json:"user_id"`
CompanyName string `json:"company_name"`
UnifiedSocialCode string `json:"unified_social_code"`
LegalPersonName string `json:"legal_person_name"`
LegalPersonID string `json:"legal_person_id"`
LegalPersonPhone string `json:"legal_person_phone"`
EnterpriseAddress string `json:"enterprise_address"`
AuthorizedRepName string `json:"authorized_rep_name"`
AuthorizedRepID string `json:"authorized_rep_id"`
AuthorizedRepPhone string `json:"authorized_rep_phone"`
AuthorizedRepIDImageURLs string `json:"authorized_rep_id_image_urls"` // JSON 字符串或解析后数组
BusinessLicenseImageURL string `json:"business_license_image_url"`
OfficePlaceImageURLs string `json:"office_place_image_urls"` // JSON 数组字符串
APIUsage string `json:"api_usage"`
ScenarioAttachmentURLs string `json:"scenario_attachment_urls"`
Status string `json:"status"`
SubmitAt time.Time `json:"submit_at"`
VerifiedAt *time.Time `json:"verified_at,omitempty"`
FailedAt *time.Time `json:"failed_at,omitempty"`
FailureReason string `json:"failure_reason,omitempty"`
CertificationStatus string `json:"certification_status,omitempty"` // 以状态机为准
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// AdminSubmitRecordsListResponse 管理端提交记录列表响应
type AdminSubmitRecordsListResponse struct {
Items []*AdminSubmitRecordItem `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}
// ================ 响应构建辅助方法 ================ // ================ 响应构建辅助方法 ================
// NewCertificationListResponse 创建认证列表响应 // NewCertificationListResponse 创建认证列表响应
@@ -146,7 +194,6 @@ func NewContractSignUrlResponse(certificationID, signURL, contractURL, nextActio
return response return response
} }
// NewSystemAlert 创建系统警告 // NewSystemAlert 创建系统警告
func NewSystemAlert(level, alertType, message, metric string, value, threshold interface{}) *SystemAlert { func NewSystemAlert(level, alertType, message, metric string, value, threshold interface{}) *SystemAlert {
return &SystemAlert{ return &SystemAlert{
@@ -161,7 +208,6 @@ func NewSystemAlert(level, alertType, message, metric string, value, threshold i
} }
} }
// IsHealthy 检查系统是否健康 // IsHealthy 检查系统是否健康
func (r *SystemMonitoringResponse) IsHealthy() bool { func (r *SystemMonitoringResponse) IsHealthy() bool {
return r.SystemHealth.Overall == "healthy" return r.SystemHealth.Overall == "healthy"

File diff suppressed because it is too large Load Diff

View File

@@ -108,8 +108,10 @@ type AlipayRechargeOrderResponse struct {
// RechargeConfigResponse 充值配置响应 // RechargeConfigResponse 充值配置响应
type RechargeConfigResponse struct { type RechargeConfigResponse struct {
MinAmount string `json:"min_amount"` // 最低充值金额 MinAmount string `json:"min_amount"` // 最低充值金额
MaxAmount string `json:"max_amount"` // 最高充值金额 MaxAmount string `json:"max_amount"` // 最高充值金额
RechargeBonusEnabled bool `json:"recharge_bonus_enabled"` // 是否启用充值赠送
ApiStoreRechargeTip string `json:"api_store_recharge_tip"` // API 商店充值提示(大额/批量联系商务)
AlipayRechargeBonus []AlipayRechargeBonusRuleResponse `json:"alipay_recharge_bonus"` AlipayRechargeBonus []AlipayRechargeBonusRuleResponse `json:"alipay_recharge_bonus"`
} }

View File

@@ -14,6 +14,7 @@ import (
finance_services "tyapi-server/internal/domains/finance/services" finance_services "tyapi-server/internal/domains/finance/services"
product_repositories "tyapi-server/internal/domains/product/repositories" product_repositories "tyapi-server/internal/domains/product/repositories"
user_repositories "tyapi-server/internal/domains/user/repositories" user_repositories "tyapi-server/internal/domains/user/repositories"
"tyapi-server/internal/infrastructure/external/notification"
"tyapi-server/internal/shared/component_report" "tyapi-server/internal/shared/component_report"
"tyapi-server/internal/shared/database" "tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/export" "tyapi-server/internal/shared/export"
@@ -43,6 +44,7 @@ type FinanceApplicationServiceImpl struct {
exportManager *export.ExportManager exportManager *export.ExportManager
logger *zap.Logger logger *zap.Logger
config *config.Config config *config.Config
wechatWorkService *notification.WeChatWorkService
} }
// NewFinanceApplicationService 创建财务应用服务 // NewFinanceApplicationService 创建财务应用服务
@@ -63,6 +65,11 @@ func NewFinanceApplicationService(
config *config.Config, config *config.Config,
exportManager *export.ExportManager, exportManager *export.ExportManager,
) FinanceApplicationService { ) FinanceApplicationService {
var wechatSvc *notification.WeChatWorkService
if config != nil && config.WechatWork.WebhookURL != "" {
wechatSvc = notification.NewWeChatWorkService(config.WechatWork.WebhookURL, config.WechatWork.Secret, logger)
}
return &FinanceApplicationServiceImpl{ return &FinanceApplicationServiceImpl{
aliPayClient: aliPayClient, aliPayClient: aliPayClient,
wechatPayService: wechatPayService, wechatPayService: wechatPayService,
@@ -79,9 +86,46 @@ func NewFinanceApplicationService(
exportManager: exportManager, exportManager: exportManager,
logger: logger, logger: logger,
config: config, config: config,
wechatWorkService: wechatSvc,
} }
} }
// getUserContactInfo 获取企业名称和联系手机号(尽量用企业信息里的手机号,退化到用户登录手机号)
func (s *FinanceApplicationServiceImpl) getUserContactInfo(ctx context.Context, userID string) (companyName, phone string) {
companyName = "未知企业"
phone = ""
if userID == "" {
return
}
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, userID)
if err != nil {
s.logger.Warn("获取用户企业信息失败,使用默认企业名称",
zap.String("user_id", userID),
zap.Error(err),
)
return
}
// 登录手机号
if user.Phone != "" {
phone = user.Phone
}
// 企业名称和企业手机号
if user.EnterpriseInfo != nil {
if user.EnterpriseInfo.CompanyName != "" {
companyName = user.EnterpriseInfo.CompanyName
}
if user.EnterpriseInfo.LegalPersonPhone != "" {
phone = user.EnterpriseInfo.LegalPersonPhone
}
}
return
}
func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) { func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) {
// 调用钱包聚合服务创建钱包 // 调用钱包聚合服务创建钱包
wallet, err := s.walletService.CreateWallet(ctx, cmd.UserID) wallet, err := s.walletService.CreateWallet(ctx, cmd.UserID)
@@ -936,6 +980,33 @@ func (s *FinanceApplicationServiceImpl) processAlipayPaymentSuccess(ctx context.
zap.String("amount", amount.String()), zap.String("amount", amount.String()),
) )
// 充值成功企业微信通知(仅充值订单,且忽略发送错误)
if s.wechatWorkService != nil {
// 再次获取充值记录拿到用户ID
rechargeRecord, err := s.rechargeRecordRepo.GetByID(ctx, alipayOrder.RechargeID)
if err == nil {
companyName, phone := s.getUserContactInfo(ctx, rechargeRecord.UserID)
content := fmt.Sprintf(
"### 【天远API】用户充值成功通知\n"+
"> 企业名称:%s\n"+
"> 联系手机:%s\n"+
"> 充值渠道:支付宝\n"+
"> 充值金额:%s 元\n"+
"> 时间:%s\n",
companyName,
phone,
amount.String(),
time.Now().Format("2006-01-02 15:04:05"),
)
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
} else {
s.logger.Warn("获取充值记录失败,跳过企业微信充值通知",
zap.String("out_trade_no", outTradeNo),
zap.Error(err),
)
}
}
return nil return nil
} }
@@ -1266,17 +1337,26 @@ func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Cont
// GetRechargeConfig 获取充值配置 // GetRechargeConfig 获取充值配置
func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) { func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) {
bonus := make([]responses.AlipayRechargeBonusRuleResponse, 0, len(s.config.Wallet.AliPayRechargeBonus)) bonus := make([]responses.AlipayRechargeBonusRuleResponse, 0)
for _, rule := range s.config.Wallet.AliPayRechargeBonus { if s.config.Wallet.RechargeBonusEnabled && len(s.config.Wallet.AliPayRechargeBonus) > 0 {
bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{ bonus = make([]responses.AlipayRechargeBonusRuleResponse, 0, len(s.config.Wallet.AliPayRechargeBonus))
RechargeAmount: rule.RechargeAmount, for _, rule := range s.config.Wallet.AliPayRechargeBonus {
BonusAmount: rule.BonusAmount, bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{
}) RechargeAmount: rule.RechargeAmount,
BonusAmount: rule.BonusAmount,
})
}
}
tip := s.config.Wallet.ApiStoreRechargeTip
if tip == "" && !s.config.Wallet.RechargeBonusEnabled {
tip = "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
} }
return &responses.RechargeConfigResponse{ return &responses.RechargeConfigResponse{
MinAmount: s.config.Wallet.MinAmount, MinAmount: s.config.Wallet.MinAmount,
MaxAmount: s.config.Wallet.MaxAmount, MaxAmount: s.config.Wallet.MaxAmount,
AlipayRechargeBonus: bonus, RechargeBonusEnabled: s.config.Wallet.RechargeBonusEnabled,
ApiStoreRechargeTip: tip,
AlipayRechargeBonus: bonus,
}, nil }, nil
} }
@@ -1580,9 +1660,9 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
return nil return nil
} }
// 计算充值赠送金额(复用支付宝的赠送逻辑) // 计算充值赠送金额(复用支付宝的赠送逻辑,受 recharge_bonus_enabled 开关控制
bonusAmount := decimal.Zero bonusAmount := decimal.Zero
if len(s.config.Wallet.AliPayRechargeBonus) > 0 { if s.config.Wallet.RechargeBonusEnabled && len(s.config.Wallet.AliPayRechargeBonus) > 0 {
for i := len(s.config.Wallet.AliPayRechargeBonus) - 1; i >= 0; i-- { for i := len(s.config.Wallet.AliPayRechargeBonus) - 1; i >= 0; i-- {
rule := s.config.Wallet.AliPayRechargeBonus[i] rule := s.config.Wallet.AliPayRechargeBonus[i]
if amount.GreaterThanOrEqual(decimal.NewFromFloat(rule.RechargeAmount)) { if amount.GreaterThanOrEqual(decimal.NewFromFloat(rule.RechargeAmount)) {
@@ -1681,6 +1761,24 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
zap.String("user_id", rechargeRecord.UserID), zap.String("user_id", rechargeRecord.UserID),
) )
// 微信充值成功企业微信通知(忽略发送错误)
if s.wechatWorkService != nil {
companyName, phone := s.getUserContactInfo(ctx, rechargeRecord.UserID)
content := fmt.Sprintf(
"### 【天远API】用户充值成功通知\n"+
"> 企业名称:%s\n"+
"> 联系手机:%s\n"+
"> 充值渠道:微信\n"+
"> 充值金额:%s 元\n"+
"> 时间:%s\n",
companyName,
phone,
amount.String(),
time.Now().Format("2006-01-02 15:04:05"),
)
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
}
return nil return nil
} }

View File

@@ -7,12 +7,14 @@ import (
"time" "time"
"tyapi-server/internal/application/finance/dto" "tyapi-server/internal/application/finance/dto"
"tyapi-server/internal/config"
"tyapi-server/internal/domains/finance/entities" "tyapi-server/internal/domains/finance/entities"
finance_repo "tyapi-server/internal/domains/finance/repositories" finance_repo "tyapi-server/internal/domains/finance/repositories"
"tyapi-server/internal/domains/finance/services" "tyapi-server/internal/domains/finance/services"
"tyapi-server/internal/domains/finance/value_objects" "tyapi-server/internal/domains/finance/value_objects"
user_repo "tyapi-server/internal/domains/user/repositories" user_repo "tyapi-server/internal/domains/user/repositories"
user_service "tyapi-server/internal/domains/user/services" user_service "tyapi-server/internal/domains/user/services"
"tyapi-server/internal/infrastructure/external/notification"
"tyapi-server/internal/infrastructure/external/storage" "tyapi-server/internal/infrastructure/external/storage"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
@@ -59,8 +61,9 @@ type InvoiceApplicationServiceImpl struct {
userAggregateService user_service.UserAggregateService userAggregateService user_service.UserAggregateService
// 外部服务依赖 // 外部服务依赖
storageService *storage.QiNiuStorageService storageService *storage.QiNiuStorageService
logger *zap.Logger logger *zap.Logger
wechatWorkServer *notification.WeChatWorkService
} }
// NewInvoiceApplicationService 创建发票应用服务 // NewInvoiceApplicationService 创建发票应用服务
@@ -76,7 +79,13 @@ func NewInvoiceApplicationService(
userInvoiceInfoService services.UserInvoiceInfoService, userInvoiceInfoService services.UserInvoiceInfoService,
storageService *storage.QiNiuStorageService, storageService *storage.QiNiuStorageService,
logger *zap.Logger, logger *zap.Logger,
cfg *config.Config,
) InvoiceApplicationService { ) InvoiceApplicationService {
var wechatSvc *notification.WeChatWorkService
if cfg != nil && cfg.WechatWork.WebhookURL != "" {
wechatSvc = notification.NewWeChatWorkService(cfg.WechatWork.WebhookURL, cfg.WechatWork.Secret, logger)
}
return &InvoiceApplicationServiceImpl{ return &InvoiceApplicationServiceImpl{
invoiceRepo: invoiceRepo, invoiceRepo: invoiceRepo,
userInvoiceInfoRepo: userInvoiceInfoRepo, userInvoiceInfoRepo: userInvoiceInfoRepo,
@@ -89,6 +98,7 @@ func NewInvoiceApplicationService(
userInvoiceInfoService: userInvoiceInfoService, userInvoiceInfoService: userInvoiceInfoService,
storageService: storageService, storageService: storageService,
logger: logger, logger: logger,
wechatWorkServer: wechatSvc,
} }
} }
@@ -175,7 +185,7 @@ func (s *InvoiceApplicationServiceImpl) ApplyInvoice(ctx context.Context, userID
} }
// 10. 构建响应DTO // 10. 构建响应DTO
return &dto.InvoiceApplicationResponse{ resp := &dto.InvoiceApplicationResponse{
ID: application.ID, ID: application.ID,
UserID: application.UserID, UserID: application.UserID,
InvoiceType: application.InvoiceType, InvoiceType: application.InvoiceType,
@@ -183,7 +193,33 @@ func (s *InvoiceApplicationServiceImpl) ApplyInvoice(ctx context.Context, userID
Status: application.Status, Status: application.Status,
InvoiceInfo: invoiceInfo, InvoiceInfo: invoiceInfo,
CreatedAt: application.CreatedAt, CreatedAt: application.CreatedAt,
}, nil }
// 11. 企业微信通知(忽略发送错误),只使用企业名称和联系电话
if s.wechatWorkServer != nil {
companyName := userWithEnterprise.EnterpriseInfo.CompanyName
phone := user.Phone
if userWithEnterprise.EnterpriseInfo.LegalPersonPhone != "" {
phone = userWithEnterprise.EnterpriseInfo.LegalPersonPhone
}
content := fmt.Sprintf(
"### 【天远API】用户申请开发票\n"+
"> 企业名称:%s\n"+
"> 联系手机:%s\n"+
"> 申请开票金额:%s 元\n"+
"> 发票类型:%s\n"+
"> 申请时间:%s\n",
companyName,
phone,
application.Amount.String(),
string(application.InvoiceType),
time.Now().Format("2006-01-02 15:04:05"),
)
_ = s.wechatWorkServer.SendMarkdownMessage(ctx, content)
}
return resp, nil
} }
// GetUserInvoiceInfo 获取用户发票信息 // GetUserInvoiceInfo 获取用户发票信息
@@ -355,27 +391,56 @@ func (s *InvoiceApplicationServiceImpl) DownloadInvoiceFile(ctx context.Context,
return nil, fmt.Errorf("发票尚未通过审核") return nil, fmt.Errorf("发票尚未通过审核")
} }
// 4. 验证文件信息 fileContent, fileID, fileName, fileSize, fileURL, err := downloadInvoiceFileFromStorage(ctx, s.storageService, application)
if application.FileURL == nil || *application.FileURL == "" {
return nil, fmt.Errorf("发票文件不存在")
}
// 5. 从七牛云下载文件内容
fileContent, err := s.storageService.DownloadFile(ctx, *application.FileURL)
if err != nil { if err != nil {
return nil, fmt.Errorf("下载文件失败: %w", err) return nil, err
} }
// 6. 构建响应DTO
return &dto.FileDownloadResponse{ return &dto.FileDownloadResponse{
FileID: *application.FileID, FileID: fileID,
FileName: *application.FileName, FileName: fileName,
FileSize: *application.FileSize, FileSize: fileSize,
FileURL: *application.FileURL, FileURL: fileURL,
FileContent: fileContent, FileContent: fileContent,
}, nil }, nil
} }
// downloadInvoiceFileFromStorage 从七牛云下载发票文件(优先用 FileID 生成新 URL避免私有空间签名过期
func downloadInvoiceFileFromStorage(ctx context.Context, storageService *storage.QiNiuStorageService, application *entities.InvoiceApplication) ([]byte, string, string, int64, string, error) {
if application.FileID == nil || *application.FileID == "" {
if application.FileURL == nil || *application.FileURL == "" {
return nil, "", "", 0, "", fmt.Errorf("发票文件不存在")
}
fileContent, err := storageService.DownloadFile(ctx, *application.FileURL)
if err != nil {
return nil, "", "", 0, "", fmt.Errorf("下载文件失败: %w", err)
}
return fileContent, derefString(application.FileID), derefString(application.FileName), derefInt64(application.FileSize), *application.FileURL, nil
}
fileContent, err := storageService.DownloadFileByKey(ctx, *application.FileID)
if err != nil {
return nil, "", "", 0, "", fmt.Errorf("下载文件失败: %w", err)
}
fileURL := storageService.GetFileURL(ctx, *application.FileID)
return fileContent, *application.FileID, derefString(application.FileName), derefInt64(application.FileSize), fileURL, nil
}
func derefString(v *string) string {
if v == nil {
return ""
}
return *v
}
func derefInt64(v *int64) int64 {
if v == nil {
return 0
}
return *v
}
// GetAvailableAmount 获取可开票金额 // GetAvailableAmount 获取可开票金额
func (s *InvoiceApplicationServiceImpl) GetAvailableAmount(ctx context.Context, userID string) (*dto.AvailableAmountResponse, error) { func (s *InvoiceApplicationServiceImpl) GetAvailableAmount(ctx context.Context, userID string) (*dto.AvailableAmountResponse, error) {
// 1. 验证用户是否存在 // 1. 验证用户是否存在
@@ -728,23 +793,16 @@ func (s *AdminInvoiceApplicationServiceImpl) DownloadInvoiceFile(ctx context.Con
return nil, fmt.Errorf("发票尚未通过审核") return nil, fmt.Errorf("发票尚未通过审核")
} }
// 3. 验证文件信息 fileContent, fileID, fileName, fileSize, fileURL, err := downloadInvoiceFileFromStorage(ctx, s.storageService, application)
if application.FileURL == nil || *application.FileURL == "" {
return nil, fmt.Errorf("发票文件不存在")
}
// 4. 从七牛云下载文件内容
fileContent, err := s.storageService.DownloadFile(ctx, *application.FileURL)
if err != nil { if err != nil {
return nil, fmt.Errorf("下载文件失败: %w", err) return nil, err
} }
// 5. 构建响应DTO
return &dto.FileDownloadResponse{ return &dto.FileDownloadResponse{
FileID: *application.FileID, FileID: fileID,
FileName: *application.FileName, FileName: fileName,
FileSize: *application.FileSize, FileSize: fileSize,
FileURL: *application.FileURL, FileURL: fileURL,
FileContent: fileContent, FileContent: fileContent,
}, nil }, nil
} }

View File

@@ -47,4 +47,7 @@ type ProductApplicationService interface {
CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error
UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error
DeleteProductApiConfig(ctx context.Context, configID string) error DeleteProductApiConfig(ctx context.Context, configID string) error
// 产品字典导出
ExportProductDictionary(ctx context.Context, format string) ([]byte, error)
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"reflect" "reflect"
"sort"
"strings" "strings"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
@@ -16,6 +17,7 @@ import (
api_services "tyapi-server/internal/domains/api/services" api_services "tyapi-server/internal/domains/api/services"
"tyapi-server/internal/domains/product/entities" "tyapi-server/internal/domains/product/entities"
product_service "tyapi-server/internal/domains/product/services" product_service "tyapi-server/internal/domains/product/services"
"tyapi-server/internal/shared/export"
"tyapi-server/internal/shared/interfaces" "tyapi-server/internal/shared/interfaces"
) )
@@ -28,6 +30,7 @@ type ProductApplicationServiceImpl struct {
documentationAppService DocumentationApplicationServiceInterface documentationAppService DocumentationApplicationServiceInterface
formConfigService api_services.FormConfigService formConfigService api_services.FormConfigService
logger *zap.Logger logger *zap.Logger
exportManager *export.ExportManager
} }
// NewProductApplicationService 创建产品应用服务 // NewProductApplicationService 创建产品应用服务
@@ -38,6 +41,7 @@ func NewProductApplicationService(
documentationAppService DocumentationApplicationServiceInterface, documentationAppService DocumentationApplicationServiceInterface,
formConfigService api_services.FormConfigService, formConfigService api_services.FormConfigService,
logger *zap.Logger, logger *zap.Logger,
exportManager *export.ExportManager,
) ProductApplicationService { ) ProductApplicationService {
return &ProductApplicationServiceImpl{ return &ProductApplicationServiceImpl{
productManagementService: productManagementService, productManagementService: productManagementService,
@@ -46,6 +50,7 @@ func NewProductApplicationService(
documentationAppService: documentationAppService, documentationAppService: documentationAppService,
formConfigService: formConfigService, formConfigService: formConfigService,
logger: logger, logger: logger,
exportManager: exportManager,
} }
} }
@@ -492,24 +497,24 @@ func (s *ProductApplicationServiceImpl) GetProductByIDForUser(ctx context.Contex
// convertToProductInfoResponse 转换为产品信息响应 // convertToProductInfoResponse 转换为产品信息响应
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse { func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
response := &responses.ProductInfoResponse{ response := &responses.ProductInfoResponse{
ID: product.ID, ID: product.ID,
OldID: product.OldID, OldID: product.OldID,
Name: product.Name, Name: product.Name,
Code: product.Code, Code: product.Code,
Description: product.Description, Description: product.Description,
Content: product.Content, Content: product.Content,
CategoryID: product.CategoryID, CategoryID: product.CategoryID,
SubCategoryID: product.SubCategoryID, SubCategoryID: product.SubCategoryID,
Price: product.Price.InexactFloat64(), Price: product.Price.InexactFloat64(),
IsEnabled: product.IsEnabled, IsEnabled: product.IsEnabled,
IsPackage: product.IsPackage, IsPackage: product.IsPackage,
SellUIComponent: product.SellUIComponent, SellUIComponent: product.SellUIComponent,
UIComponentPrice: product.UIComponentPrice.InexactFloat64(), UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
SEOTitle: product.SEOTitle, SEOTitle: product.SEOTitle,
SEODescription: product.SEODescription, SEODescription: product.SEODescription,
SEOKeywords: product.SEOKeywords, SEOKeywords: product.SEOKeywords,
CreatedAt: product.CreatedAt, CreatedAt: product.CreatedAt,
UpdatedAt: product.UpdatedAt, UpdatedAt: product.UpdatedAt,
} }
// 添加一级分类信息 // 添加一级分类信息
@@ -544,27 +549,27 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
// convertToProductAdminInfoResponse 转换为管理员产品信息响应 // convertToProductAdminInfoResponse 转换为管理员产品信息响应
func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse { func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse {
response := &responses.ProductAdminInfoResponse{ response := &responses.ProductAdminInfoResponse{
ID: product.ID, ID: product.ID,
OldID: product.OldID, OldID: product.OldID,
Name: product.Name, Name: product.Name,
Code: product.Code, Code: product.Code,
Description: product.Description, Description: product.Description,
Content: product.Content, Content: product.Content,
CategoryID: product.CategoryID, CategoryID: product.CategoryID,
SubCategoryID: product.SubCategoryID, SubCategoryID: product.SubCategoryID,
Price: product.Price.InexactFloat64(), Price: product.Price.InexactFloat64(),
CostPrice: product.CostPrice.InexactFloat64(), CostPrice: product.CostPrice.InexactFloat64(),
Remark: product.Remark, Remark: product.Remark,
IsEnabled: product.IsEnabled, IsEnabled: product.IsEnabled,
IsVisible: product.IsVisible, // 管理员可以看到可见状态 IsVisible: product.IsVisible, // 管理员可以看到可见状态
IsPackage: product.IsPackage, IsPackage: product.IsPackage,
SellUIComponent: product.SellUIComponent, SellUIComponent: product.SellUIComponent,
UIComponentPrice: product.UIComponentPrice.InexactFloat64(), UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
SEOTitle: product.SEOTitle, SEOTitle: product.SEOTitle,
SEODescription: product.SEODescription, SEODescription: product.SEODescription,
SEOKeywords: product.SEOKeywords, SEOKeywords: product.SEOKeywords,
CreatedAt: product.CreatedAt, CreatedAt: product.CreatedAt,
UpdatedAt: product.UpdatedAt, UpdatedAt: product.UpdatedAt,
} }
// 添加一级分类信息 // 添加一级分类信息
@@ -994,101 +999,103 @@ func (s *ProductApplicationServiceImpl) mergeRequestParamsFromDTOs(ctx context.C
// getDTOMap 获取API代码到DTO结构体的映射复用form_config_service的逻辑 // getDTOMap 获取API代码到DTO结构体的映射复用form_config_service的逻辑
func (s *ProductApplicationServiceImpl) getDTOMap() map[string]interface{} { func (s *ProductApplicationServiceImpl) getDTOMap() map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"IVYZ9363": &dto.IVYZ9363Req{}, "IVYZ9363": &dto.IVYZ9363Req{},
"IVYZ385E": &dto.IVYZ385EReq{}, "IVYZ385E": &dto.IVYZ385EReq{},
"IVYZ5733": &dto.IVYZ5733Req{}, "IVYZ5733": &dto.IVYZ5733Req{},
"FLXG3D56": &dto.FLXG3D56Req{}, "FLXG3D56": &dto.FLXG3D56Req{},
"FLXG75FE": &dto.FLXG75FEReq{}, "FLXG75FE": &dto.FLXG75FEReq{},
"FLXG0V3B": &dto.FLXG0V3BReq{}, "FLXG0V3B": &dto.FLXG0V3BReq{},
"FLXG0V4B": &dto.FLXG0V4BReq{}, "FLXG0V4B": &dto.FLXG0V4BReq{},
"FLXG54F5": &dto.FLXG54F5Req{}, "FLXG54F5": &dto.FLXG54F5Req{},
"FLXG162A": &dto.FLXG162AReq{}, "FLXG162A": &dto.FLXG162AReq{},
"FLXG0687": &dto.FLXG0687Req{}, "FLXG0687": &dto.FLXG0687Req{},
"FLXGBC21": &dto.FLXGBC21Req{}, "FLXGBC21": &dto.FLXGBC21Req{},
"FLXG970F": &dto.FLXG970FReq{}, "FLXG970F": &dto.FLXG970FReq{},
"FLXG5876": &dto.FLXG5876Req{}, "FLXG5876": &dto.FLXG5876Req{},
"FLXG9687": &dto.FLXG9687Req{}, "FLXG9687": &dto.FLXG9687Req{},
"FLXGC9D1": &dto.FLXGC9D1Req{}, "FLXGC9D1": &dto.FLXGC9D1Req{},
"FLXGCA3D": &dto.FLXGCA3DReq{}, "FLXGCA3D": &dto.FLXGCA3DReq{},
"FLXGDEC7": &dto.FLXGDEC7Req{}, "FLXGDEC7": &dto.FLXGDEC7Req{},
"JRZQ0A03": &dto.JRZQ0A03Req{}, "JRZQ0A03": &dto.JRZQ0A03Req{},
"JRZQ4AA8": &dto.JRZQ4AA8Req{}, "JRZQ4AA8": &dto.JRZQ4AA8Req{},
"JRZQ8203": &dto.JRZQ8203Req{}, "JRZQ8203": &dto.JRZQ8203Req{},
"JRZQDCBE": &dto.JRZQDCBEReq{}, "JRZQDCBE": &dto.JRZQDCBEReq{},
"QYGL2ACD": &dto.QYGL2ACDReq{}, "QYGL2ACD": &dto.QYGL2ACDReq{},
"QYGL6F2D": &dto.QYGL6F2DReq{}, "QYGL6F2D": &dto.QYGL6F2DReq{},
"QYGL45BD": &dto.QYGL45BDReq{}, "QYGL45BD": &dto.QYGL45BDReq{},
"QYGL8261": &dto.QYGL8261Req{}, "QYGL8261": &dto.QYGL8261Req{},
"QYGL8271": &dto.QYGL8271Req{}, "QYGL8271": &dto.QYGL8271Req{},
"QYGLB4C0": &dto.QYGLB4C0Req{}, "QYGLB4C0": &dto.QYGLB4C0Req{},
"QYGL23T7": &dto.QYGL23T7Req{}, "QYGL23T7": &dto.QYGL23T7Req{},
"QYGL5A3C": &dto.QYGL5A3CReq{}, "QYGL5A3C": &dto.QYGL5A3CReq{},
"QYGL8B4D": &dto.QYGL8B4DReq{}, "QYGL8B4D": &dto.QYGL8B4DReq{},
"QYGL9E2F": &dto.QYGL9E2FReq{}, "QYGL9E2F": &dto.QYGL9E2FReq{},
"QYGL7C1A": &dto.QYGL7C1AReq{}, "QYGL7C1A": &dto.QYGL7C1AReq{},
"QYGL3F8E": &dto.QYGL3F8EReq{}, "QYGL3F8E": &dto.QYGL3F8EReq{},
"YYSY4B37": &dto.YYSY4B37Req{}, "YYSY4B37": &dto.YYSY4B37Req{},
"YYSY4B21": &dto.YYSY4B21Req{}, "YYSY4B21": &dto.YYSY4B21Req{},
"YYSY6F2E": &dto.YYSY6F2EReq{}, "YYSY6F2E": &dto.YYSY6F2EReq{},
"YYSY09CD": &dto.YYSY09CDReq{}, "YYSY09CD": &dto.YYSY09CDReq{},
"IVYZ0B03": &dto.IVYZ0B03Req{}, "IVYZ0B03": &dto.IVYZ0B03Req{},
"YYSYBE08": &dto.YYSYBE08Req{}, "YYSYBE08": &dto.YYSYBE08Req{},
"YYSYD50F": &dto.YYSYD50FReq{}, "YYSYBE08TEST": &dto.YYSYBE08Req{},
"YYSYF7DB": &dto.YYSYF7DBReq{}, "YYSYD50F": &dto.YYSYD50FReq{},
"IVYZ9A2B": &dto.IVYZ9A2BReq{}, "YYSYF7DB": &dto.YYSYF7DBReq{},
"IVYZ7F2A": &dto.IVYZ7F2AReq{}, "IVYZ9A2B": &dto.IVYZ9A2BReq{},
"IVYZ4E8B": &dto.IVYZ4E8BReq{}, "IVYZ7F2A": &dto.IVYZ7F2AReq{},
"IVYZ1C9D": &dto.IVYZ1C9DReq{}, "IVYZ4E8B": &dto.IVYZ4E8BReq{},
"IVYZGZ08": &dto.IVYZGZ08Req{}, "IVYZ1C9D": &dto.IVYZ1C9DReq{},
"FLXG8A3F": &dto.FLXG8A3FReq{}, "IVYZGZ08": &dto.IVYZGZ08Req{},
"FLXG5B2E": &dto.FLXG5B2EReq{}, "FLXG8A3F": &dto.FLXG8A3FReq{},
"COMB298Y": &dto.COMB298YReq{}, "FLXG5B2E": &dto.FLXG5B2EReq{},
"COMB86PM": &dto.COMB86PMReq{}, "COMB298Y": &dto.COMB298YReq{},
"QCXG7A2B": &dto.QCXG7A2BReq{}, "COMB86PM": &dto.COMB86PMReq{},
"COMENT01": &dto.COMENT01Req{}, "QCXG7A2B": &dto.QCXG7A2BReq{},
"JRZQ09J8": &dto.JRZQ09J8Req{}, "COMENT01": &dto.COMENT01Req{},
"FLXGDEA8": &dto.FLXGDEA8Req{}, "JRZQ09J8": &dto.JRZQ09J8Req{},
"FLXGDEA9": &dto.FLXGDEA9Req{}, "FLXGDEA8": &dto.FLXGDEA8Req{},
"JRZQ1D09": &dto.JRZQ1D09Req{}, "FLXGDEA9": &dto.FLXGDEA9Req{},
"IVYZ2A8B": &dto.IVYZ2A8BReq{}, "JRZQ1D09": &dto.JRZQ1D09Req{},
"IVYZ7C9D": &dto.IVYZ7C9DReq{}, "IVYZ2A8B": &dto.IVYZ2A8BReq{},
"IVYZ5E3F": &dto.IVYZ5E3FReq{}, "IVYZ7C9D": &dto.IVYZ7C9DReq{},
"YYSY4F2E": &dto.YYSY4F2EReq{}, "IVYZ5E3F": &dto.IVYZ5E3FReq{},
"YYSY8B1C": &dto.YYSY8B1CReq{}, "YYSY4F2E": &dto.YYSY4F2EReq{},
"YYSY6D9A": &dto.YYSY6D9AReq{}, "YYSY8B1C": &dto.YYSY8B1CReq{},
"YYSY3E7F": &dto.YYSY3E7FReq{}, "YYSY6D9A": &dto.YYSY6D9AReq{},
"FLXG5A3B": &dto.FLXG5A3BReq{}, "YYSY3E7F": &dto.YYSY3E7FReq{},
"FLXG9C1D": &dto.FLXG9C1DReq{}, "FLXG5A3B": &dto.FLXG5A3BReq{},
"FLXG2E8F": &dto.FLXG2E8FReq{}, "FLXG9C1D": &dto.FLXG9C1DReq{},
"JRZQ3C7B": &dto.JRZQ3C7BReq{}, "FLXG2E8F": &dto.FLXG2E8FReq{},
"JRZQ8A2D": &dto.JRZQ8A2DReq{}, "JRZQ3C7B": &dto.JRZQ3C7BReq{},
"JRZQ5E9F": &dto.JRZQ5E9FReq{}, "JRZQ8A2D": &dto.JRZQ8A2DReq{},
"JRZQ4B6C": &dto.JRZQ4B6CReq{}, "JRZQ5E9F": &dto.JRZQ5E9FReq{},
"JRZQ7F1A": &dto.JRZQ7F1AReq{}, "JRZQ4B6C": &dto.JRZQ4B6CReq{},
"DWBG6A2C": &dto.DWBG6A2CReq{}, "JRZQ7F1A": &dto.JRZQ7F1AReq{},
"DWBG8B4D": &dto.DWBG8B4DReq{}, "DWBG6A2C": &dto.DWBG6A2CReq{},
"FLXG8B4D": &dto.FLXG8B4DReq{}, "DWBG8B4D": &dto.DWBG8B4DReq{},
"IVYZ81NC": &dto.IVYZ81NCReq{}, "FLXG8B4D": &dto.FLXG8B4DReq{},
"IVYZ7F3A": &dto.IVYZ7F3AReq{}, "IVYZ81NC": &dto.IVYZ81NCReq{},
"IVYZ3P9M": &dto.IVYZ3P9MReq{}, "IVYZ2MN6": &dto.IVYZ2MN6Req{},
"IVYZ3A7F": &dto.IVYZ3A7FReq{}, "IVYZ7F3A": &dto.IVYZ7F3AReq{},
"IVYZ9D2E": &dto.IVYZ9D2EReq{}, "IVYZ3P9M": &dto.IVYZ3P9MReq{},
"DWBG7F3A": &dto.DWBG7F3AReq{}, "IVYZ3A7F": &dto.IVYZ3A7FReq{},
"YYSY8F3A": &dto.YYSY8F3AReq{}, "IVYZ9D2E": &dto.IVYZ9D2EReq{},
"QCXG9P1C": &dto.QCXG9P1CReq{}, "DWBG7F3A": &dto.DWBG7F3AReq{},
"JRZQ9E2A": &dto.JRZQ9E2AReq{}, "YYSY8F3A": &dto.YYSY8F3AReq{},
"YYSY9A1B": &dto.YYSY9A1BReq{}, "QCXG9P1C": &dto.QCXG9P1CReq{},
"YYSY8C2D": &dto.YYSY8C2DReq{}, "JRZQ9E2A": &dto.JRZQ9E2AReq{},
"YYSY7D3E": &dto.YYSY7D3EReq{}, "YYSY9A1B": &dto.YYSY9A1BReq{},
"YYSY9E4A": &dto.YYSY9E4AReq{}, "YYSY8C2D": &dto.YYSY8C2DReq{},
"JRZQ6F2A": &dto.JRZQ6F2AReq{}, "YYSY7D3E": &dto.YYSY7D3EReq{},
"JRZQ8B3C": &dto.JRZQ8B3CReq{}, "YYSY9E4A": &dto.YYSY9E4AReq{},
"JRZQ9D4E": &dto.JRZQ9D4EReq{}, "JRZQ6F2A": &dto.JRZQ6F2AReq{},
"FLXG7E8F": &dto.FLXG7E8FReq{}, "JRZQ8B3C": &dto.JRZQ8B3CReq{},
"QYGL5F6A": &dto.QYGL5F6AReq{}, "JRZQ9D4E": &dto.JRZQ9D4EReq{},
"IVYZ6G7H": &dto.IVYZ6G7HReq{}, "FLXG7E8F": &dto.FLXG7E8FReq{},
"IVYZ8I9J": &dto.IVYZ8I9JReq{}, "QYGL5F6A": &dto.QYGL5F6AReq{},
"JRZQ0L85": &dto.JRZQ0L85Req{}, "IVYZ6G7H": &dto.IVYZ6G7HReq{},
"IVYZ8I9J": &dto.IVYZ8I9JReq{},
"JRZQ0L85": &dto.JRZQ0L85Req{},
} }
} }
@@ -1238,3 +1245,120 @@ func (s *ProductApplicationServiceImpl) mapFieldTypeToDocType(frontendType strin
return "string" return "string"
} }
} }
// ExportProductDictionary 导出产品字典
func (s *ProductApplicationServiceImpl) ExportProductDictionary(ctx context.Context, format string) ([]byte, error) {
// 查询所有启用且可见的产品及其分类信息
products, err := s.productManagementService.GetAllProductsForDictionary(ctx)
if err != nil {
s.logger.Error("获取产品字典数据失败", zap.Error(err))
return nil, err
}
if len(products) == 0 {
return nil, fmt.Errorf("没有找到符合条件的产品数据")
}
// 按分类名称分组整理数据
type categoryInfo struct {
name string
sort int
}
categoryGroups := make(map[string][]map[string]interface{}) // 使用分类名称作为key
categorySortMap := make(map[string]int) // 分类名称到sort值的映射
var categoryNames []string // 保持分类顺序
for _, product := range products {
// 获取分类信息
categoryName := "未分类"
categorySort := 999999 // 默认排序值,确保未分类的产品排在最后
if product.Category != nil {
categoryName = product.Category.Name
categorySort = product.Category.Sort
// 如果有二级分类,添加到分类名称中
if product.SubCategory != nil && product.SubCategory.Name != "" {
categoryName = categoryName + " / " + product.SubCategory.Name
}
}
// 如果分类不存在,初始化并添加到顺序列表
if _, exists := categoryGroups[categoryName]; !exists {
categoryGroups[categoryName] = []map[string]interface{}{}
categoryNames = append(categoryNames, categoryName)
categorySortMap[categoryName] = categorySort
}
// 添加产品到对应分类
productInfo := map[string]interface{}{
"category": categoryName,
"product_code": product.Code,
"product_name": product.Name,
"description": product.Description,
}
categoryGroups[categoryName] = append(categoryGroups[categoryName], productInfo)
}
// 按分类的sort值对分类名称进行排序
sort.Slice(categoryNames, func(i, j int) bool {
return categorySortMap[categoryNames[i]] < categorySortMap[categoryNames[j]]
})
// 准备导出数据
headers := []string{"分类", "产品编码", "产品名称", "产品简介"}
columnWidths := []float64{25, 15, 20, 40}
// 构建数据行
var data [][]interface{}
for _, categoryName := range categoryNames {
productsInCategory := categoryGroups[categoryName]
for i, product := range productsInCategory {
// 只有每个分类的第一个产品才显示分类名称
var categoryNameForRow interface{}
if i == 0 {
categoryNameForRow = categoryName
} else {
categoryNameForRow = ""
}
row := []interface{}{
categoryNameForRow,
product["product_code"],
product["product_name"],
product["description"],
}
data = append(data, row)
}
}
// 计算需要合并的行
mergedRegions := [][]int{}
currentRow := 1 // 从第1行开始第0行是表头
for _, categoryName := range categoryNames {
productsCount := len(categoryGroups[categoryName])
if productsCount > 1 {
// 合并相同分类的单元格从当前行开始合并productsCount行第0列
// Excel格式[startRow, startCol, endRow, endCol]
mergedRegions = append(mergedRegions, []int{
currentRow, // startRow
0, // startCol (分类列)
currentRow + productsCount - 1, // endRow
0, // endCol
})
}
currentRow += productsCount
}
// 创建导出配置
config := &export.ExportConfig{
SheetName: "产品字典",
Headers: headers,
Data: data,
ColumnWidths: columnWidths,
MergedRegions: mergedRegions,
}
// 使用导出管理器生成文件
return s.exportManager.Export(ctx, config, format)
}

View File

@@ -0,0 +1,16 @@
package product
import "context"
// SelfSubscribePolicy 是否允许用户在控制台自助发起「订阅产品」
type SelfSubscribePolicy interface {
Allow(ctx context.Context, userID string) (allowed bool, message string, err error)
}
// DefaultAllowSelfSubscribe 未装配下属模块时:恒允许
type DefaultAllowSelfSubscribe struct{}
// Allow 恒允许
func (DefaultAllowSelfSubscribe) Allow(_ context.Context, _ string) (bool, string, error) {
return true, "", nil
}

View File

@@ -23,6 +23,7 @@ type SubscriptionApplicationServiceImpl struct {
productSubscriptionService *product_service.ProductSubscriptionService productSubscriptionService *product_service.ProductSubscriptionService
userRepo user_repositories.UserRepository userRepo user_repositories.UserRepository
apiCallRepository domain_api_repo.ApiCallRepository apiCallRepository domain_api_repo.ApiCallRepository
selfSubscribePolicy SelfSubscribePolicy
logger *zap.Logger logger *zap.Logger
} }
@@ -31,12 +32,17 @@ func NewSubscriptionApplicationService(
productSubscriptionService *product_service.ProductSubscriptionService, productSubscriptionService *product_service.ProductSubscriptionService,
userRepo user_repositories.UserRepository, userRepo user_repositories.UserRepository,
apiCallRepository domain_api_repo.ApiCallRepository, apiCallRepository domain_api_repo.ApiCallRepository,
selfSubscribePolicy SelfSubscribePolicy,
logger *zap.Logger, logger *zap.Logger,
) SubscriptionApplicationService { ) SubscriptionApplicationService {
if selfSubscribePolicy == nil {
selfSubscribePolicy = DefaultAllowSelfSubscribe{}
}
return &SubscriptionApplicationServiceImpl{ return &SubscriptionApplicationServiceImpl{
productSubscriptionService: productSubscriptionService, productSubscriptionService: productSubscriptionService,
userRepo: userRepo, userRepo: userRepo,
apiCallRepository: apiCallRepository, apiCallRepository: apiCallRepository,
selfSubscribePolicy: selfSubscribePolicy,
logger: logger, logger: logger,
} }
} }
@@ -157,7 +163,17 @@ func (s *SubscriptionApplicationServiceImpl) BatchUpdateSubscriptionPrices(ctx c
// CreateSubscription 创建订阅 // CreateSubscription 创建订阅
// 业务流程1. 创建订阅 // 业务流程1. 创建订阅
func (s *SubscriptionApplicationServiceImpl) CreateSubscription(ctx context.Context, cmd *commands.CreateSubscriptionCommand) error { func (s *SubscriptionApplicationServiceImpl) CreateSubscription(ctx context.Context, cmd *commands.CreateSubscriptionCommand) error {
_, err := s.productSubscriptionService.CreateSubscription(ctx, cmd.UserID, cmd.ProductID) allow, msg, err := s.selfSubscribePolicy.Allow(ctx, cmd.UserID)
if err != nil {
return err
}
if !allow {
if msg == "" {
msg = "当前账号不允许自助订阅"
}
return fmt.Errorf("%s", msg)
}
_, err = s.productSubscriptionService.CreateSubscription(ctx, cmd.UserID, cmd.ProductID)
return err return err
} }

View File

@@ -0,0 +1,108 @@
package commands
// SubPortalRegisterCommand 子站注册(邀请码必填)
type SubPortalRegisterCommand struct {
Phone string `json:"phone" binding:"required"`
Password string `json:"password" binding:"required"`
ConfirmPassword string `json:"confirm_password" binding:"required"`
Code string `json:"code" binding:"required"`
InviteToken string `json:"invite_token" binding:"required"`
}
// ListChildApiCallsCommand 下属 API 调用记录查询
type ListChildApiCallsCommand struct {
ParentUserID string
ChildUserID string `form:"child_user_id" binding:"required"`
Page int `form:"page"`
PageSize int `form:"page_size"`
TransactionID string `form:"transaction_id"`
ProductName string `form:"product_name"`
Status string `form:"status"`
StartTime string `form:"start_time"`
EndTime string `form:"end_time"`
}
// ListSubordinatesCommand 下属列表查询
type ListSubordinatesCommand struct {
ParentUserID string
Page int `form:"page"`
PageSize int `form:"page_size"`
Remark string `form:"remark"`
Phone string `form:"phone"`
CompanyName string `form:"company_name"`
}
// UpdateSubordinateRemarkCommand 更新下属备注
type UpdateSubordinateRemarkCommand struct {
ParentUserID string
ChildUserID string `json:"child_user_id" binding:"required"`
Remark string `json:"remark"`
}
// CreateInvitationCommand 主账号创建邀请
type CreateInvitationCommand struct {
ParentUserID string
// ExpiresInHours 可选0 或不传为永久有效100年
ExpiresInHours int `json:"expires_in_hours"`
}
// AllocateToChildCommand 主账号向下属划余额
type AllocateToChildCommand struct {
ParentUserID string
ChildUserID string `json:"child_user_id" binding:"required"`
Amount string `json:"amount" binding:"required"`
VerifyCode string `json:"verify_code" binding:"required,len=6"`
}
// AssignChildSubscriptionCommand 为下属代配订阅
type AssignChildSubscriptionCommand struct {
ParentUserID string
ChildUserID string `json:"child_user_id" binding:"required"`
ProductID string `json:"product_id" binding:"required"`
Price string `json:"price" binding:"required"`
UIComponentPrice string `json:"ui_component_price"`
}
// ListChildAllocationsCommand 下属划拨记录查询
type ListChildAllocationsCommand struct {
ParentUserID string
ChildUserID string `json:"child_user_id" form:"child_user_id" binding:"required"`
Page int `json:"page" form:"page"`
PageSize int `json:"page_size" form:"page_size"`
}
// ListChildSubscriptionsCommand 下属订阅列表查询
type ListChildSubscriptionsCommand struct {
ParentUserID string
ChildUserID string `json:"child_user_id" form:"child_user_id" binding:"required"`
}
// RemoveChildSubscriptionCommand 删除下属订阅
type RemoveChildSubscriptionCommand struct {
ParentUserID string
ChildUserID string `json:"child_user_id" binding:"required"`
SubscriptionID string `json:"subscription_id" binding:"required"`
}
// PurchaseChildQuotaCommand 主账号为子账号购买调用额度
type PurchaseChildQuotaCommand struct {
ParentUserID string
ChildUserID string `json:"child_user_id" binding:"required"`
ProductID string `json:"product_id" binding:"required"`
CallCount int64 `json:"call_count" binding:"required,min=1"`
VerifyCode string `json:"verify_code" binding:"required,len=6"`
}
// ListChildQuotaPurchasesCommand 下属额度购买记录查询
type ListChildQuotaPurchasesCommand struct {
ParentUserID string
ChildUserID string `json:"child_user_id" form:"child_user_id" binding:"required"`
Page int `json:"page" form:"page"`
PageSize int `json:"page_size" form:"page_size"`
}
// ListChildQuotaAccountsCommand 下属额度账户查询
type ListChildQuotaAccountsCommand struct {
ParentUserID string
ChildUserID string `json:"child_user_id" form:"child_user_id" binding:"required"`
}

View File

@@ -0,0 +1,91 @@
package responses
import "time"
// CreateInvitationResponse 创建邀请
type CreateInvitationResponse struct {
InviteToken string `json:"invite_token" description:"主账号固定邀请码,可重复使用"`
InviteURL string `json:"invite_url" description:"子站注册完整链接"`
ExpiresAt time.Time `json:"expires_at"`
InvitationID string `json:"invitation_id"`
}
// SubordinateProductQuotaItem 下属产品额度
type SubordinateProductQuotaItem struct {
ProductID string `json:"product_id"`
ProductName string `json:"product_name"`
AvailableQuota int64 `json:"available_quota"`
}
// SubordinateListItem 下属一条
type SubordinateListItem struct {
ChildUserID string `json:"child_user_id"`
Phone string `json:"phone,omitempty"`
LinkID string `json:"link_id"`
Remark string `json:"remark"`
RegisteredAt time.Time `json:"registered_at"`
CompanyName string `json:"company_name"`
IsCertified bool `json:"is_certified"`
Balance string `json:"balance"`
ProductQuotas []SubordinateProductQuotaItem `json:"product_quotas"`
}
// SubordinateListResponse 列表
type SubordinateListResponse struct {
Total int64 `json:"total"`
Items []SubordinateListItem `json:"items"`
}
// SubPortalRegisterResponse 子站注册
type SubPortalRegisterResponse struct {
ID string `json:"id"`
Phone string `json:"phone"`
}
// ChildAllocationItem 下属划拨记录
type ChildAllocationItem struct {
ID string `json:"id"`
Amount string `json:"amount"`
BusinessRef string `json:"business_ref"`
CreatedAt time.Time `json:"created_at"`
}
// ChildAllocationListResponse 下属划拨记录列表
type ChildAllocationListResponse struct {
Total int64 `json:"total"`
Items []ChildAllocationItem `json:"items"`
}
// ChildSubscriptionItem 下属订阅项
type ChildSubscriptionItem struct {
ID string `json:"id"`
ProductID string `json:"product_id"`
Price string `json:"price"`
UIComponentPrice string `json:"ui_component_price"`
CreatedAt time.Time `json:"created_at"`
}
// ChildQuotaPurchaseItem 下属额度购买记录
type ChildQuotaPurchaseItem struct {
ID string `json:"id"`
ProductID string `json:"product_id"`
CallCount int64 `json:"call_count"`
UnitPrice string `json:"unit_price"`
TotalAmount string `json:"total_amount"`
BusinessRef string `json:"business_ref"`
CreatedAt time.Time `json:"created_at"`
}
// ChildQuotaPurchaseListResponse 下属额度购买记录列表
type ChildQuotaPurchaseListResponse struct {
Total int64 `json:"total"`
Items []ChildQuotaPurchaseItem `json:"items"`
}
// ChildQuotaAccountItem 下属产品额度账户
type ChildQuotaAccountItem struct {
ProductID string `json:"product_id"`
TotalQuota int64 `json:"total_quota"`
UsedQuota int64 `json:"used_quota"`
AvailableQuota int64 `json:"available_quota"`
}

View File

@@ -0,0 +1,35 @@
package subordinate
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"math/big"
)
const (
// 邀请码固定 6 位,字符集为大写字母+数字
inviteTokenLength = 6
inviteTokenCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)
// HashInviteToken 邀请码 SHA256 十六进制
func HashInviteToken(raw string) string {
sum := sha256.Sum256([]byte(raw))
return hex.EncodeToString(sum[:])
}
// GenerateInviteToken 生成随机邀请明文与存储用哈希
func GenerateInviteToken() (raw string, hash string, err error) {
token := make([]byte, inviteTokenLength)
charsetSize := big.NewInt(int64(len(inviteTokenCharset)))
for i := range token {
n, e := rand.Int(rand.Reader, charsetSize)
if e != nil {
return "", "", e
}
token[i] = inviteTokenCharset[n.Int64()]
}
raw = string(token)
return raw, HashInviteToken(raw), nil
}

View File

@@ -0,0 +1,26 @@
package subordinate
import "testing"
func TestGenerateInviteTokenFormat(t *testing.T) {
raw, hash, err := GenerateInviteToken()
if err != nil {
t.Fatalf("GenerateInviteToken error: %v", err)
}
if len(raw) != inviteTokenLength {
t.Fatalf("unexpected token length: got %d, want %d", len(raw), inviteTokenLength)
}
for _, ch := range raw {
isUpper := ch >= 'A' && ch <= 'Z'
isDigit := ch >= '0' && ch <= '9'
if !isUpper && !isDigit {
t.Fatalf("token contains invalid char: %q", ch)
}
}
if hash != HashInviteToken(raw) {
t.Fatalf("hash mismatch for token")
}
}

View File

@@ -0,0 +1,30 @@
package subordinate
import (
"context"
"tyapi-server/internal/application/product"
"tyapi-server/internal/domains/subordinate/repositories"
)
// BlockSelfSubscribeForSubordinate 子账号禁止自助订
type BlockSelfSubscribeForSubordinate struct {
repo repositories.SubordinateRepository
}
// NewBlockSelfSubscribeForSubordinate 构造
func NewBlockSelfSubscribeForSubordinate(repo repositories.SubordinateRepository) product.SelfSubscribePolicy {
return &BlockSelfSubscribeForSubordinate{repo: repo}
}
// Allow 若为主账号的下属则拒绝
func (p *BlockSelfSubscribeForSubordinate) Allow(ctx context.Context, userID string) (bool, string, error) {
ok, err := p.repo.IsUserSubordinate(ctx, userID)
if err != nil {
return false, "", err
}
if ok {
return false, "子账号需由主账号配置订阅", nil
}
return true, "", nil
}

View File

@@ -0,0 +1,27 @@
package subordinate
import (
"context"
api_dto "tyapi-server/internal/application/api/dto"
"tyapi-server/internal/application/subordinate/dto/commands"
"tyapi-server/internal/application/subordinate/dto/responses"
)
// SubordinateApplicationService 下属账号:邀请/注册/划款/代配
type SubordinateApplicationService interface {
RegisterSubPortal(ctx context.Context, cmd *commands.SubPortalRegisterCommand) (*responses.SubPortalRegisterResponse, error)
CreateInvitation(ctx context.Context, cmd *commands.CreateInvitationCommand) (*responses.CreateInvitationResponse, error)
ListMySubordinates(ctx context.Context, cmd *commands.ListSubordinatesCommand) (*responses.SubordinateListResponse, error)
UpdateSubordinateRemark(ctx context.Context, cmd *commands.UpdateSubordinateRemarkCommand) error
AllocateToChild(ctx context.Context, cmd *commands.AllocateToChildCommand) error
ListChildAllocations(ctx context.Context, cmd *commands.ListChildAllocationsCommand) (*responses.ChildAllocationListResponse, error)
AssignChildSubscription(ctx context.Context, cmd *commands.AssignChildSubscriptionCommand) error
ListChildSubscriptions(ctx context.Context, cmd *commands.ListChildSubscriptionsCommand) ([]responses.ChildSubscriptionItem, error)
RemoveChildSubscription(ctx context.Context, cmd *commands.RemoveChildSubscriptionCommand) error
PurchaseChildQuota(ctx context.Context, cmd *commands.PurchaseChildQuotaCommand) error
ListChildQuotaPurchases(ctx context.Context, cmd *commands.ListChildQuotaPurchasesCommand) (*responses.ChildQuotaPurchaseListResponse, error)
ListChildQuotaAccounts(ctx context.Context, cmd *commands.ListChildQuotaAccountsCommand) ([]responses.ChildQuotaAccountItem, error)
ListChildApiCalls(ctx context.Context, cmd *commands.ListChildApiCallsCommand) (*api_dto.ApiCallListResponse, error)
ListMyQuotaAccounts(ctx context.Context, userID string) ([]responses.ChildQuotaAccountItem, error)
}

View File

@@ -0,0 +1,767 @@
package subordinate
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/google/uuid"
"github.com/shopspring/decimal"
"go.uber.org/zap"
api_app "tyapi-server/internal/application/api"
api_dto "tyapi-server/internal/application/api/dto"
"tyapi-server/internal/application/subordinate/dto/commands"
"tyapi-server/internal/application/subordinate/dto/responses"
"tyapi-server/internal/config"
"tyapi-server/internal/domains/finance/repositories"
productentities "tyapi-server/internal/domains/product/entities"
product_service "tyapi-server/internal/domains/product/services"
product_repositories "tyapi-server/internal/domains/product/repositories"
subentities "tyapi-server/internal/domains/subordinate/entities"
subrepositories "tyapi-server/internal/domains/subordinate/repositories"
user_entities "tyapi-server/internal/domains/user/entities"
user_repositories "tyapi-server/internal/domains/user/repositories"
domain_user_services "tyapi-server/internal/domains/user/services"
"tyapi-server/internal/shared/database"
shared_interfaces "tyapi-server/internal/shared/interfaces"
)
// SubordinateApplicationServiceImpl 实现
type SubordinateApplicationServiceImpl struct {
subRepo subrepositories.SubordinateRepository
userAgg domain_user_services.UserAggregateService
smsService *domain_user_services.SMSCodeService
productSub *product_service.ProductSubscriptionService
productRepo product_repositories.ProductRepository
cfg *config.Config
txm *database.TransactionManager
walletRepo repositories.WalletRepository
userRepo user_repositories.UserRepository
apiApp api_app.ApiApplicationService
logger *zap.Logger
}
// NewSubordinateApplicationService 构造
func NewSubordinateApplicationService(
subRepo subrepositories.SubordinateRepository,
userAgg domain_user_services.UserAggregateService,
smsService *domain_user_services.SMSCodeService,
productSub *product_service.ProductSubscriptionService,
productRepo product_repositories.ProductRepository,
cfg *config.Config,
txm *database.TransactionManager,
walletRepo repositories.WalletRepository,
userRepo user_repositories.UserRepository,
apiApp api_app.ApiApplicationService,
logger *zap.Logger,
) SubordinateApplicationService {
return &SubordinateApplicationServiceImpl{
subRepo: subRepo,
userAgg: userAgg,
smsService: smsService,
productSub: productSub,
productRepo: productRepo,
cfg: cfg,
txm: txm,
walletRepo: walletRepo,
userRepo: userRepo,
apiApp: apiApp,
logger: logger,
}
}
// RegisterSubPortal 子站注册
func (s *SubordinateApplicationServiceImpl) RegisterSubPortal(ctx context.Context, cmd *commands.SubPortalRegisterCommand) (*responses.SubPortalRegisterResponse, error) {
if cmd.Password != cmd.ConfirmPassword {
return nil, fmt.Errorf("两次输入的密码不一致")
}
if err := s.smsService.VerifyCode(ctx, cmd.Phone, cmd.Code, user_entities.SMSSceneRegister); err != nil {
return nil, fmt.Errorf("验证码错误或已过期")
}
var resp *responses.SubPortalRegisterResponse
err := s.txm.ExecuteInTx(ctx, func(txCtx context.Context) error {
inv, err := s.subRepo.FindInvitationByTokenHash(txCtx, HashInviteToken(strings.TrimSpace(cmd.InviteToken)))
if err != nil {
return err
}
if inv == nil {
return fmt.Errorf("邀请码无效")
}
switch inv.Status {
case subentities.InvitationStatusRevoked:
return fmt.Errorf("邀请码已失效")
case subentities.InvitationStatusConsumed:
return fmt.Errorf("邀请码已失效,请联系主账号获取新邀请码")
case subentities.InvitationStatusPending:
// 固定邀请码可重复使用,注册后不核销
default:
return fmt.Errorf("邀请码无效")
}
now := time.Now()
if now.After(inv.ExpiresAt) {
return fmt.Errorf("邀请码已过期")
}
u, createErr := s.userAgg.CreateUser(txCtx, cmd.Phone, cmd.Password)
if createErr != nil {
return createErr
}
link := &subentities.UserSubordinateLink{
ParentUserID: inv.ParentUserID,
ChildUserID: u.ID,
InvitationID: &inv.ID,
Status: subentities.LinkStatusActive,
}
if linkErr := s.subRepo.CreateLink(txCtx, link); linkErr != nil {
s.logger.Error("创建主从关系失败", zap.Error(linkErr), zap.String("user_id", u.ID))
return fmt.Errorf("注册失败,请重试或联系主账号")
}
resp = &responses.SubPortalRegisterResponse{ID: u.ID, Phone: u.Phone}
return nil
})
if err != nil {
return nil, err
}
return resp, nil
}
// CreateInvitation 获取或创建主账号固定邀请码(可重复使用)
func (s *SubordinateApplicationServiceImpl) CreateInvitation(ctx context.Context, cmd *commands.CreateInvitationCommand) (*responses.CreateInvitationResponse, error) {
existing, err := s.subRepo.FindActiveInvitationByParent(ctx, cmd.ParentUserID)
if err != nil {
return nil, err
}
if existing != nil {
if raw := strings.TrimSpace(existing.Token); raw != "" {
return s.buildInvitationResponse(existing, raw)
}
// 历史 pending 记录未存明文,无法找回,继续创建新的固定邀请码
}
hours := cmd.ExpiresInHours
if hours <= 0 {
// 永久有效设置100年后过期
hours = 24 * 365 * 100
}
raw, hash, err := GenerateInviteToken()
if err != nil {
return nil, fmt.Errorf("生成邀请失败")
}
inv := &subentities.SubordinateInvitation{
ParentUserID: cmd.ParentUserID,
Token: raw,
TokenHash: hash,
ExpiresAt: time.Now().Add(time.Duration(hours) * time.Hour),
Status: subentities.InvitationStatusPending,
}
if err := s.subRepo.CreateInvitation(ctx, inv); err != nil {
return nil, err
}
return s.buildInvitationResponse(inv, raw)
}
func (s *SubordinateApplicationServiceImpl) buildInvitationResponse(inv *subentities.SubordinateInvitation, raw string) (*responses.CreateInvitationResponse, error) {
base := strings.TrimSpace(os.Getenv("SUB_PORTAL_BASE_URL"))
if base == "" {
base = s.cfg.App.SubPortalBaseURL
}
base = strings.TrimRight(base, "/")
if base == "" {
return nil, fmt.Errorf("子账号域名未配置,请设置 app.sub_portal_base_url 或环境变量 SUB_PORTAL_BASE_URL")
}
// 与前端同仓路由一致:/sub/auth/register 为子账号专用注册页
inviteURL := base + "/sub/auth/register?invite=" + raw
return &responses.CreateInvitationResponse{
InviteToken: raw,
InviteURL: inviteURL,
ExpiresAt: inv.ExpiresAt,
InvitationID: inv.ID,
}, nil
}
// ListMySubordinates 主账号的下属
func (s *SubordinateApplicationServiceImpl) ListMySubordinates(ctx context.Context, cmd *commands.ListSubordinatesCommand) (*responses.SubordinateListResponse, error) {
page := cmd.Page
pageSize := cmd.PageSize
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
offset := (page - 1) * pageSize
filter := subrepositories.SubordinateListFilter{
Remark: strings.TrimSpace(cmd.Remark),
Phone: strings.TrimSpace(cmd.Phone),
CompanyName: strings.TrimSpace(cmd.CompanyName),
}
links, total, err := s.subRepo.ListChildrenByParent(ctx, cmd.ParentUserID, filter, pageSize, offset)
if err != nil {
return nil, err
}
childIDs := make([]string, 0, len(links))
for _, ln := range links {
childIDs = append(childIDs, ln.ChildUserID)
}
quotaByUser := make(map[string][]*subentities.UserProductQuotaAccount)
if len(childIDs) > 0 {
accounts, quotaErr := s.subRepo.ListQuotaAccountsByUserIDs(ctx, childIDs)
if quotaErr != nil {
return nil, quotaErr
}
for _, account := range accounts {
quotaByUser[account.UserID] = append(quotaByUser[account.UserID], account)
}
}
productNameMap := make(map[string]string)
if s.productRepo != nil {
productIDSet := make(map[string]struct{})
for _, accounts := range quotaByUser {
for _, account := range accounts {
productIDSet[account.ProductID] = struct{}{}
}
}
if len(productIDSet) > 0 {
productIDs := make([]string, 0, len(productIDSet))
for id := range productIDSet {
productIDs = append(productIDs, id)
}
products, productErr := s.productRepo.FindProductsByIDs(ctx, productIDs)
if productErr != nil {
s.logger.Warn("批量获取产品名称失败", zap.Error(productErr))
} else {
for _, product := range products {
if product != nil {
productNameMap[product.ID] = product.Name
}
}
}
}
}
items := make([]responses.SubordinateListItem, 0, len(links))
for _, ln := range links {
phone := ""
companyName := "未认证"
isCertified := false
registeredAt := ln.CreatedAt
balance := "0.00"
if u, e := s.userRepo.GetByIDWithEnterpriseInfo(ctx, ln.ChildUserID); e == nil {
phone = u.Phone
isCertified = u.IsCertified
registeredAt = u.CreatedAt
if u.EnterpriseInfo != nil && strings.TrimSpace(u.EnterpriseInfo.CompanyName) != "" {
companyName = strings.TrimSpace(u.EnterpriseInfo.CompanyName)
}
} else {
s.logger.Warn("获取下属用户失败", zap.String("child_id", ln.ChildUserID), zap.Error(e))
}
if w, e := s.walletRepo.GetByUserID(ctx, ln.ChildUserID); e == nil && w != nil {
balance = w.Balance.StringFixed(2)
}
productQuotas := make([]responses.SubordinateProductQuotaItem, 0)
for _, account := range quotaByUser[ln.ChildUserID] {
name := productNameMap[account.ProductID]
if name == "" {
name = account.ProductID
}
productQuotas = append(productQuotas, responses.SubordinateProductQuotaItem{
ProductID: account.ProductID,
ProductName: name,
AvailableQuota: account.AvailableQuota,
})
}
items = append(items, responses.SubordinateListItem{
ChildUserID: ln.ChildUserID,
Phone: phone,
LinkID: ln.ID,
Remark: ln.Remark,
RegisteredAt: registeredAt,
CompanyName: companyName,
IsCertified: isCertified,
Balance: balance,
ProductQuotas: productQuotas,
})
}
return &responses.SubordinateListResponse{Total: total, Items: items}, nil
}
// UpdateSubordinateRemark 更新下属备注
func (s *SubordinateApplicationServiceImpl) UpdateSubordinateRemark(ctx context.Context, cmd *commands.UpdateSubordinateRemarkCommand) error {
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
if err != nil {
return err
}
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
return fmt.Errorf("该用户不是您的有效下属")
}
remark := strings.TrimSpace(cmd.Remark)
if len([]rune(remark)) > 255 {
return fmt.Errorf("备注不能超过255个字符")
}
lnk.Remark = remark
return s.subRepo.UpdateLink(ctx, lnk)
}
// AllocateToChild 划款
func (s *SubordinateApplicationServiceImpl) AllocateToChild(ctx context.Context, cmd *commands.AllocateToChildCommand) error {
amount, err := decimal.NewFromString(strings.TrimSpace(cmd.Amount))
if err != nil || !amount.GreaterThan(decimal.Zero) {
return fmt.Errorf("金额必须大于0")
}
parentUser, err := s.userRepo.GetByID(ctx, cmd.ParentUserID)
if err != nil {
return fmt.Errorf("主账号信息获取失败")
}
if err := s.smsService.VerifyCode(ctx, parentUser.Phone, strings.TrimSpace(cmd.VerifyCode), user_entities.SMSSceneLogin); err != nil {
return fmt.Errorf("验证码错误或已过期")
}
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
if err != nil {
return err
}
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
return fmt.Errorf("该用户不是您的有效下属")
}
bizRef := uuid.New().String()
return s.txm.ExecuteInTx(ctx, func(txCtx context.Context) error {
ok, err := s.walletRepo.UpdateBalanceByUserID(txCtx, cmd.ParentUserID, amount, "subtract")
if err != nil {
return err
}
if !ok {
return fmt.Errorf("主账号扣款失败,请重试")
}
ok2, err := s.walletRepo.UpdateBalanceByUserID(txCtx, cmd.ChildUserID, amount, "add")
if err != nil {
return err
}
if !ok2 {
return fmt.Errorf("向下属入账失败,请重试")
}
alloc := &subentities.SubordinateWalletAllocation{
FromUserID: cmd.ParentUserID,
ToUserID: cmd.ChildUserID,
Amount: amount,
BusinessRef: bizRef,
OperatorUserID: cmd.ParentUserID,
}
return s.subRepo.CreateWalletAllocation(txCtx, alloc)
})
}
// ListChildAllocations 下属划拨记录
func (s *SubordinateApplicationServiceImpl) ListChildAllocations(ctx context.Context, cmd *commands.ListChildAllocationsCommand) (*responses.ChildAllocationListResponse, error) {
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
if err != nil {
return nil, err
}
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
return nil, fmt.Errorf("该用户不是您的有效下属")
}
page := cmd.Page
pageSize := cmd.PageSize
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
offset := (page - 1) * pageSize
rows, total, err := s.subRepo.ListWalletAllocationsByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID, pageSize, offset)
if err != nil {
return nil, err
}
items := make([]responses.ChildAllocationItem, 0, len(rows))
for _, row := range rows {
items = append(items, responses.ChildAllocationItem{
ID: row.ID,
Amount: row.Amount.StringFixed(2),
BusinessRef: row.BusinessRef,
CreatedAt: row.CreatedAt,
})
}
return &responses.ChildAllocationListResponse{
Total: total,
Items: items,
}, nil
}
// AssignChildSubscription 代配订阅
func (s *SubordinateApplicationServiceImpl) AssignChildSubscription(ctx context.Context, cmd *commands.AssignChildSubscriptionCommand) error {
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
if err != nil {
return err
}
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
return fmt.Errorf("该用户不是您的有效下属")
}
price, err := decimal.NewFromString(strings.TrimSpace(cmd.Price))
if err != nil {
return fmt.Errorf("价格格式无效")
}
parentSub, err := s.productSub.GetUserSubscribedProduct(ctx, cmd.ParentUserID, cmd.ProductID)
if err != nil {
return err
}
if parentSub == nil {
return fmt.Errorf("主账号未订阅该产品,无法为下属代配")
}
if price.LessThan(parentSub.Price) {
return fmt.Errorf("下属订阅价不能低于主账号对该产品的订阅价")
}
uip := parentSub.UIComponentPrice
if strings.TrimSpace(cmd.UIComponentPrice) != "" {
p, err2 := decimal.NewFromString(strings.TrimSpace(cmd.UIComponentPrice))
if err2 != nil {
return fmt.Errorf("UI组件价格格式无效")
}
if p.LessThan(parentSub.UIComponentPrice) {
return fmt.Errorf("下属 UI 组合价不能低于主账号的 UI 组合价")
}
uip = p
}
existing, err := s.productSub.GetUserSubscribedProduct(ctx, cmd.ChildUserID, cmd.ProductID)
if err != nil {
return err
}
if existing == nil {
newSub := &productentities.Subscription{
UserID: cmd.ChildUserID,
ProductID: cmd.ProductID,
Price: price,
UIComponentPrice: uip,
}
return s.productSub.SaveSubscription(ctx, newSub)
}
existing.Price = price
existing.UIComponentPrice = uip
return s.productSub.SaveSubscription(ctx, existing)
}
// ListChildSubscriptions 下属订阅列表
func (s *SubordinateApplicationServiceImpl) ListChildSubscriptions(ctx context.Context, cmd *commands.ListChildSubscriptionsCommand) ([]responses.ChildSubscriptionItem, error) {
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
if err != nil {
return nil, err
}
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
return nil, fmt.Errorf("该用户不是您的有效下属")
}
subs, err := s.productSub.GetUserSubscriptions(ctx, cmd.ChildUserID)
if err != nil {
return nil, err
}
items := make([]responses.ChildSubscriptionItem, 0, len(subs))
for _, sub := range subs {
items = append(items, responses.ChildSubscriptionItem{
ID: sub.ID,
ProductID: sub.ProductID,
Price: sub.Price.StringFixed(2),
UIComponentPrice: sub.UIComponentPrice.StringFixed(2),
CreatedAt: sub.CreatedAt,
})
}
return items, nil
}
// RemoveChildSubscription 删除下属订阅
func (s *SubordinateApplicationServiceImpl) RemoveChildSubscription(ctx context.Context, cmd *commands.RemoveChildSubscriptionCommand) error {
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
if err != nil {
return err
}
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
return fmt.Errorf("该用户不是您的有效下属")
}
sub, err := s.productSub.GetSubscriptionByID(ctx, cmd.SubscriptionID)
if err != nil {
return fmt.Errorf("订阅不存在")
}
if sub.UserID != cmd.ChildUserID {
return fmt.Errorf("订阅不属于该下属")
}
return s.productSub.CancelSubscription(ctx, cmd.SubscriptionID)
}
// PurchaseChildQuota 主账号为子账号购买调用额度(按子账号订阅价结算)
func (s *SubordinateApplicationServiceImpl) PurchaseChildQuota(ctx context.Context, cmd *commands.PurchaseChildQuotaCommand) error {
if cmd.CallCount <= 0 {
return fmt.Errorf("购买次数必须大于0")
}
parentUser, err := s.userRepo.GetByID(ctx, cmd.ParentUserID)
if err != nil {
return fmt.Errorf("主账号信息获取失败")
}
if err := s.smsService.VerifyCode(ctx, parentUser.Phone, strings.TrimSpace(cmd.VerifyCode), user_entities.SMSSceneLogin); err != nil {
return fmt.Errorf("验证码错误或已过期")
}
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
if err != nil {
return err
}
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
return fmt.Errorf("该用户不是您的有效下属")
}
parentSub, err := s.productSub.GetUserSubscribedProduct(ctx, cmd.ParentUserID, cmd.ProductID)
if err != nil {
return err
}
if parentSub == nil {
return fmt.Errorf("主账号未订阅该产品,无法购买额度")
}
if !parentSub.Price.GreaterThan(decimal.Zero) {
return fmt.Errorf("主账号订阅价格异常,无法购买额度")
}
callCountDec := decimal.NewFromInt(cmd.CallCount)
totalAmount := parentSub.Price.Mul(callCountDec)
if !totalAmount.GreaterThan(decimal.Zero) {
return fmt.Errorf("购买金额必须大于0")
}
bizRef := uuid.New().String()
return s.txm.ExecuteInTx(ctx, func(txCtx context.Context) error {
// 购买额度前自动确保子账号存在该产品订阅,并统一为主账号订阅价
childSub, err := s.productSub.GetUserSubscribedProduct(txCtx, cmd.ChildUserID, cmd.ProductID)
if err != nil {
return err
}
if childSub == nil {
newSub := &productentities.Subscription{
UserID: cmd.ChildUserID,
ProductID: cmd.ProductID,
Price: parentSub.Price,
UIComponentPrice: parentSub.UIComponentPrice,
}
if err := s.productSub.SaveSubscription(txCtx, newSub); err != nil {
return fmt.Errorf("为下属创建订阅失败: %w", err)
}
} else {
childSub.Price = parentSub.Price
childSub.UIComponentPrice = parentSub.UIComponentPrice
if err := s.productSub.SaveSubscription(txCtx, childSub); err != nil {
return fmt.Errorf("更新下属订阅失败: %w", err)
}
}
ok, err := s.walletRepo.UpdateBalanceByUserID(txCtx, cmd.ParentUserID, totalAmount, "subtract")
if err != nil {
return err
}
if !ok {
return fmt.Errorf("主账号扣款失败,请重试")
}
account, err := s.subRepo.FindQuotaAccount(txCtx, cmd.ChildUserID, cmd.ProductID)
if err != nil {
return err
}
var beforeAvailable int64
if account == nil {
account = &subentities.UserProductQuotaAccount{
UserID: cmd.ChildUserID,
ProductID: cmd.ProductID,
TotalQuota: cmd.CallCount,
UsedQuota: 0,
AvailableQuota: cmd.CallCount,
}
beforeAvailable = 0
if err := s.subRepo.CreateQuotaAccount(txCtx, account); err != nil {
return err
}
} else {
beforeAvailable = account.AvailableQuota
account.TotalQuota += cmd.CallCount
account.AvailableQuota += cmd.CallCount
if err := s.subRepo.UpdateQuotaAccount(txCtx, account); err != nil {
return err
}
}
purchase := &subentities.SubordinateQuotaPurchase{
ParentUserID: cmd.ParentUserID,
ChildUserID: cmd.ChildUserID,
ProductID: cmd.ProductID,
CallCount: cmd.CallCount,
UnitPrice: parentSub.Price,
TotalAmount: totalAmount,
BusinessRef: bizRef,
OperatorUserID: cmd.ParentUserID,
}
if err := s.subRepo.CreateQuotaPurchase(txCtx, purchase); err != nil {
return err
}
ledger := &subentities.UserProductQuotaLedger{
UserID: cmd.ChildUserID,
ProductID: cmd.ProductID,
ChangeType: subentities.QuotaLedgerChangeTypePurchaseForSub,
DeltaQuota: cmd.CallCount,
BeforeQuota: beforeAvailable,
AfterQuota: beforeAvailable + cmd.CallCount,
SourceID: purchase.ID,
OperatorID: cmd.ParentUserID,
Remark: "主账号为子账号购买额度",
}
return s.subRepo.CreateQuotaLedger(txCtx, ledger)
})
}
// ListChildQuotaPurchases 下属额度购买记录
func (s *SubordinateApplicationServiceImpl) ListChildQuotaPurchases(ctx context.Context, cmd *commands.ListChildQuotaPurchasesCommand) (*responses.ChildQuotaPurchaseListResponse, error) {
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
if err != nil {
return nil, err
}
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
return nil, fmt.Errorf("该用户不是您的有效下属")
}
page := cmd.Page
pageSize := cmd.PageSize
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
offset := (page - 1) * pageSize
rows, total, err := s.subRepo.ListQuotaPurchasesByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID, pageSize, offset)
if err != nil {
return nil, err
}
items := make([]responses.ChildQuotaPurchaseItem, 0, len(rows))
for _, row := range rows {
items = append(items, responses.ChildQuotaPurchaseItem{
ID: row.ID,
ProductID: row.ProductID,
CallCount: row.CallCount,
UnitPrice: row.UnitPrice.StringFixed(2),
TotalAmount: row.TotalAmount.StringFixed(2),
BusinessRef: row.BusinessRef,
CreatedAt: row.CreatedAt,
})
}
return &responses.ChildQuotaPurchaseListResponse{
Total: total,
Items: items,
}, nil
}
// ListChildQuotaAccounts 下属额度账户
func (s *SubordinateApplicationServiceImpl) ListChildQuotaAccounts(ctx context.Context, cmd *commands.ListChildQuotaAccountsCommand) ([]responses.ChildQuotaAccountItem, error) {
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
if err != nil {
return nil, err
}
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
return nil, fmt.Errorf("该用户不是您的有效下属")
}
accounts, err := s.subRepo.ListQuotaAccountsByUser(ctx, cmd.ChildUserID)
if err != nil {
return nil, err
}
items := make([]responses.ChildQuotaAccountItem, 0, len(accounts))
for _, account := range accounts {
items = append(items, responses.ChildQuotaAccountItem{
ProductID: account.ProductID,
TotalQuota: account.TotalQuota,
UsedQuota: account.UsedQuota,
AvailableQuota: account.AvailableQuota,
})
}
return items, nil
}
// ListChildApiCalls 主账号查看下属 API 调用记录
func (s *SubordinateApplicationServiceImpl) ListChildApiCalls(ctx context.Context, cmd *commands.ListChildApiCallsCommand) (*api_dto.ApiCallListResponse, error) {
if s.apiApp == nil {
return nil, fmt.Errorf("API 服务未初始化")
}
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
if err != nil {
return nil, err
}
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
return nil, fmt.Errorf("该用户不是您的有效下属")
}
page := cmd.Page
pageSize := cmd.PageSize
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 10
}
filters := make(map[string]interface{})
if strings.TrimSpace(cmd.TransactionID) != "" {
filters["transaction_id"] = strings.TrimSpace(cmd.TransactionID)
}
if strings.TrimSpace(cmd.ProductName) != "" {
filters["product_name"] = strings.TrimSpace(cmd.ProductName)
}
if strings.TrimSpace(cmd.Status) != "" {
filters["status"] = strings.TrimSpace(cmd.Status)
}
if strings.TrimSpace(cmd.StartTime) != "" {
if t, parseErr := time.Parse("2006-01-02 15:04:05", strings.TrimSpace(cmd.StartTime)); parseErr == nil {
filters["start_time"] = t
}
}
if strings.TrimSpace(cmd.EndTime) != "" {
if t, parseErr := time.Parse("2006-01-02 15:04:05", strings.TrimSpace(cmd.EndTime)); parseErr == nil {
filters["end_time"] = t
}
}
return s.apiApp.GetUserApiCalls(ctx, cmd.ChildUserID, filters, shared_interfaces.ListOptions{
Page: page,
PageSize: pageSize,
Sort: "created_at",
Order: "desc",
})
}
// ListMyQuotaAccounts 查询当前用户额度账户(通用能力,适配所有用户)
func (s *SubordinateApplicationServiceImpl) ListMyQuotaAccounts(ctx context.Context, userID string) ([]responses.ChildQuotaAccountItem, error) {
accounts, err := s.subRepo.ListQuotaAccountsByUser(ctx, userID)
if err != nil {
return nil, err
}
items := make([]responses.ChildQuotaAccountItem, 0, len(accounts))
for _, account := range accounts {
items = append(items, responses.ChildQuotaAccountItem{
ProductID: account.ProductID,
TotalQuota: account.TotalQuota,
UsedQuota: account.UsedQuota,
AvailableQuota: account.AvailableQuota,
})
}
return items, nil
}

View File

@@ -43,10 +43,20 @@ type ResetPasswordCommand struct {
} }
// SendCodeCommand 发送验证码命令 // SendCodeCommand 发送验证码命令
// @Description 发送短信验证码请求参数 // @Description 发送短信验证码请求参数。只接收编码后的data字段使用自定义编码方案非Base64
type SendCodeCommand struct { type SendCodeCommand struct {
Phone string `json:"phone" binding:"required,phone" example:"13800138000"` // 编码后的数据使用自定义编码方案的JSON字符串包含所有参数phone, scene, timestamp, nonce, signature
Scene string `json:"scene" binding:"required,oneof=register login change_password reset_password bind unbind certification" example:"register"` Data string `json:"data" binding:"required" example:"K8mN9vP2sL7kH3oB6yC1zA5uF0qE9tW..."` // 自定义编码后的数据
// 阿里云滑块验证码参数(直接接收,不参与编码)
CaptchaVerifyParam string `json:"captchaVerifyParam,omitempty" example:"..."` // 滑块验证码验证参数
// 以下字段从data解码后填充不直接接收
Phone string `json:"-"` // 从data解码后获取
Scene string `json:"-"` // 从data解码后获取
Timestamp int64 `json:"-"` // 从data解码后获取
Nonce string `json:"-"` // 从data解码后获取
Signature string `json:"-"` // 从data解码后获取
} }
// UpdateProfileCommand 更新用户信息命令 // UpdateProfileCommand 更新用户信息命令

View File

@@ -51,6 +51,8 @@ type UserProfileResponse struct {
IsCertified bool `json:"is_certified" example:"false"` IsCertified bool `json:"is_certified" example:"false"`
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"` CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"` UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`
// AccountKind standalone=普通/主站用户 subordinate=主账号邀请的下属
AccountKind string `json:"account_kind" example:"standalone"`
} }
// SendCodeResponse 发送验证码响应 // SendCodeResponse 发送验证码响应

View File

@@ -9,11 +9,11 @@ import (
func (s *UserApplicationServiceImpl) SendCode(ctx context.Context, cmd *commands.SendCodeCommand, clientIP, userAgent string) error { func (s *UserApplicationServiceImpl) SendCode(ctx context.Context, cmd *commands.SendCodeCommand, clientIP, userAgent string) error {
// 1. 检查频率限制 // 1. 检查频率限制
if err := s.smsCodeService.CheckRateLimit(ctx, cmd.Phone, entities.SMSScene(cmd.Scene)); err != nil { if err := s.smsCodeService.CheckRateLimit(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), clientIP, userAgent); err != nil {
return err return err
} }
err := s.smsCodeService.SendCode(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), clientIP, userAgent) err := s.smsCodeService.SendCode(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), clientIP, userAgent, cmd.CaptchaVerifyParam)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -13,6 +13,7 @@ import (
"tyapi-server/internal/domains/user/entities" "tyapi-server/internal/domains/user/entities"
"tyapi-server/internal/domains/user/events" "tyapi-server/internal/domains/user/events"
user_service "tyapi-server/internal/domains/user/services" user_service "tyapi-server/internal/domains/user/services"
"tyapi-server/internal/shared/auth"
"tyapi-server/internal/shared/interfaces" "tyapi-server/internal/shared/interfaces"
"tyapi-server/internal/shared/middleware" "tyapi-server/internal/shared/middleware"
) )
@@ -27,6 +28,7 @@ type UserApplicationServiceImpl struct {
contractService user_service.ContractAggregateService contractService user_service.ContractAggregateService
eventBus interfaces.EventBus eventBus interfaces.EventBus
jwtAuth *middleware.JWTAuthMiddleware jwtAuth *middleware.JWTAuthMiddleware
accountKindProvider interfaces.AccountKindProvider
logger *zap.Logger logger *zap.Logger
} }
@@ -39,6 +41,7 @@ func NewUserApplicationService(
contractService user_service.ContractAggregateService, contractService user_service.ContractAggregateService,
eventBus interfaces.EventBus, eventBus interfaces.EventBus,
jwtAuth *middleware.JWTAuthMiddleware, jwtAuth *middleware.JWTAuthMiddleware,
accountKindProvider interfaces.AccountKindProvider,
logger *zap.Logger, logger *zap.Logger,
) UserApplicationService { ) UserApplicationService {
return &UserApplicationServiceImpl{ return &UserApplicationServiceImpl{
@@ -49,6 +52,7 @@ func NewUserApplicationService(
contractService: contractService, contractService: contractService,
eventBus: eventBus, eventBus: eventBus,
jwtAuth: jwtAuth, jwtAuth: jwtAuth,
accountKindProvider: accountKindProvider,
logger: logger, logger: logger,
} }
} }
@@ -90,76 +94,16 @@ func (s *UserApplicationServiceImpl) LoginWithPassword(ctx context.Context, cmd
return nil, err return nil, err
} }
// 2. 生成包含用户类型的token // 2. 账号类型(下属/普通)
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone, user.UserType) accountKind := auth.AccountKindStandalone
if err != nil { if s.accountKindProvider != nil {
s.logger.Error("生成令牌失败", zap.Error(err)) if k, err := s.accountKindProvider.AccountKind(ctx, user.ID); err == nil && k != "" {
return nil, fmt.Errorf("生成访问令牌失败") accountKind = k
}
// 3. 如果是管理员,更新登录统计
if user.IsAdmin() {
if err := s.userAggregateService.UpdateLoginStats(ctx, user.ID); err != nil {
s.logger.Error("更新登录统计失败", zap.Error(err))
}
// 重新获取用户信息以获取最新的登录统计
updatedUser, err := s.userAggregateService.GetUserByID(ctx, user.ID)
if err != nil {
s.logger.Error("重新获取用户信息失败", zap.Error(err))
} else {
user = updatedUser
} }
} }
// 4. 获取用户权限(仅管理员) // 3. 生成包含用户类型的 token
var permissions []string accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone, user.UserType, accountKind)
if user.IsAdmin() {
permissions, err = s.userAuthService.GetUserPermissions(ctx, user)
if err != nil {
s.logger.Error("获取用户权限失败", zap.Error(err))
permissions = []string{}
}
}
// 5. 构建用户信息
userProfile := &responses.UserProfileResponse{
ID: user.ID,
Phone: user.Phone,
Username: user.Username,
UserType: user.UserType,
IsActive: user.Active,
LastLoginAt: user.LastLoginAt,
LoginCount: user.LoginCount,
Permissions: permissions,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
return &responses.LoginUserResponse{
User: userProfile,
AccessToken: accessToken,
TokenType: "Bearer",
ExpiresIn: 86400, // 24h
LoginMethod: "password",
}, nil
}
// LoginWithSMS 短信验证码登录
// 业务流程1. 验证短信验证码 2. 验证用户登录状态 3. 生成访问令牌 4. 更新登录统计 5. 获取用户权限
func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *commands.LoginWithSMSCommand) (*responses.LoginUserResponse, error) {
// 1. 验证短信验证码
if err := s.smsCodeService.VerifyCode(ctx, cmd.Phone, cmd.Code, entities.SMSSceneLogin); err != nil {
return nil, fmt.Errorf("验证码错误或已过期")
}
// 2. 验证用户登录状态
user, err := s.userAuthService.ValidateUserLogin(ctx, cmd.Phone)
if err != nil {
return nil, err
}
// 3. 生成包含用户类型的token
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone, user.UserType)
if err != nil { if err != nil {
s.logger.Error("生成令牌失败", zap.Error(err)) s.logger.Error("生成令牌失败", zap.Error(err))
return nil, fmt.Errorf("生成访问令牌失败") return nil, fmt.Errorf("生成访问令牌失败")
@@ -201,6 +145,83 @@ func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *comm
Permissions: permissions, Permissions: permissions,
CreatedAt: user.CreatedAt, CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt, UpdatedAt: user.UpdatedAt,
AccountKind: accountKind,
}
return &responses.LoginUserResponse{
User: userProfile,
AccessToken: accessToken,
TokenType: "Bearer",
ExpiresIn: 86400, // 24h
LoginMethod: "password",
}, nil
}
// LoginWithSMS 短信验证码登录
// 业务流程1. 验证短信验证码 2. 验证用户登录状态 3. 生成访问令牌 4. 更新登录统计 5. 获取用户权限
func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *commands.LoginWithSMSCommand) (*responses.LoginUserResponse, error) {
// 1. 验证短信验证码
if err := s.smsCodeService.VerifyCode(ctx, cmd.Phone, cmd.Code, entities.SMSSceneLogin); err != nil {
return nil, fmt.Errorf("验证码错误或已过期")
}
// 2. 验证用户登录状态
user, err := s.userAuthService.ValidateUserLogin(ctx, cmd.Phone)
if err != nil {
return nil, err
}
accountKind := auth.AccountKindStandalone
if s.accountKindProvider != nil {
if k, err := s.accountKindProvider.AccountKind(ctx, user.ID); err == nil && k != "" {
accountKind = k
}
}
// 3. 生成包含用户类型的 token
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone, user.UserType, accountKind)
if err != nil {
s.logger.Error("生成令牌失败", zap.Error(err))
return nil, fmt.Errorf("生成访问令牌失败")
}
// 4. 如果是管理员,更新登录统计
if user.IsAdmin() {
if err := s.userAggregateService.UpdateLoginStats(ctx, user.ID); err != nil {
s.logger.Error("更新登录统计失败", zap.Error(err))
}
// 重新获取用户信息以获取最新的登录统计
updatedUser, err := s.userAggregateService.GetUserByID(ctx, user.ID)
if err != nil {
s.logger.Error("重新获取用户信息失败", zap.Error(err))
} else {
user = updatedUser
}
}
// 5. 获取用户权限(仅管理员)
var permissions []string
if user.IsAdmin() {
permissions, err = s.userAuthService.GetUserPermissions(ctx, user)
if err != nil {
s.logger.Error("获取用户权限失败", zap.Error(err))
permissions = []string{}
}
}
// 6. 构建用户信息
userProfile := &responses.UserProfileResponse{
ID: user.ID,
Phone: user.Phone,
Username: user.Username,
UserType: user.UserType,
IsActive: user.Active,
LastLoginAt: user.LastLoginAt,
LoginCount: user.LoginCount,
Permissions: permissions,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
AccountKind: accountKind,
} }
return &responses.LoginUserResponse{ return &responses.LoginUserResponse{
@@ -212,12 +233,6 @@ func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *comm
}, nil }, nil
} }
// SendSMS 发送短信验证码
// 业务流程1. 发送短信验证码
func (s *UserApplicationServiceImpl) SendSMS(ctx context.Context, cmd *commands.SendCodeCommand) error {
return s.smsCodeService.SendCode(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), "", "")
}
// ChangePassword 修改密码 // ChangePassword 修改密码
// 业务流程1. 修改用户密码 // 业务流程1. 修改用户密码
func (s *UserApplicationServiceImpl) ChangePassword(ctx context.Context, cmd *commands.ChangePasswordCommand) error { func (s *UserApplicationServiceImpl) ChangePassword(ctx context.Context, cmd *commands.ChangePasswordCommand) error {
@@ -268,6 +283,12 @@ func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID
Permissions: permissions, Permissions: permissions,
CreatedAt: user.CreatedAt, CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt, UpdatedAt: user.UpdatedAt,
AccountKind: auth.AccountKindStandalone,
}
if s.accountKindProvider != nil {
if k, err := s.accountKindProvider.AccountKind(ctx, userID); err == nil && k != "" {
userProfile.AccountKind = k
}
} }
// 4. 添加企业信息 // 4. 添加企业信息
@@ -345,19 +366,19 @@ func (s *UserApplicationServiceImpl) ListUsers(ctx context.Context, query *queri
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress, EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
CreatedAt: user.EnterpriseInfo.CreatedAt, CreatedAt: user.EnterpriseInfo.CreatedAt,
} }
// 获取企业合同信息 // 获取企业合同信息
contracts, err := s.contractService.FindByUserID(ctx, user.ID) contracts, err := s.contractService.FindByUserID(ctx, user.ID)
if err == nil && len(contracts) > 0 { if err == nil && len(contracts) > 0 {
contractItems := make([]*responses.ContractInfoItem, 0, len(contracts)) contractItems := make([]*responses.ContractInfoItem, 0, len(contracts))
for _, contract := range contracts { for _, contract := range contracts {
contractItems = append(contractItems, &responses.ContractInfoItem{ contractItems = append(contractItems, &responses.ContractInfoItem{
ID: contract.ID, ID: contract.ID,
ContractName: contract.ContractName, ContractName: contract.ContractName,
ContractType: string(contract.ContractType), ContractType: string(contract.ContractType),
ContractTypeName: contract.GetContractTypeName(), ContractTypeName: contract.GetContractTypeName(),
ContractFileURL: contract.ContractFileURL, ContractFileURL: contract.ContractFileURL,
CreatedAt: contract.CreatedAt, CreatedAt: contract.CreatedAt,
}) })
} }
item.EnterpriseInfo.Contracts = contractItems item.EnterpriseInfo.Contracts = contractItems
@@ -417,19 +438,19 @@ func (s *UserApplicationServiceImpl) GetUserDetail(ctx context.Context, userID s
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress, EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
CreatedAt: user.EnterpriseInfo.CreatedAt, CreatedAt: user.EnterpriseInfo.CreatedAt,
} }
// 获取企业合同信息 // 获取企业合同信息
contracts, err := s.contractService.FindByUserID(ctx, user.ID) contracts, err := s.contractService.FindByUserID(ctx, user.ID)
if err == nil && len(contracts) > 0 { if err == nil && len(contracts) > 0 {
contractItems := make([]*responses.ContractInfoItem, 0, len(contracts)) contractItems := make([]*responses.ContractInfoItem, 0, len(contracts))
for _, contract := range contracts { for _, contract := range contracts {
contractItems = append(contractItems, &responses.ContractInfoItem{ contractItems = append(contractItems, &responses.ContractInfoItem{
ID: contract.ID, ID: contract.ID,
ContractName: contract.ContractName, ContractName: contract.ContractName,
ContractType: string(contract.ContractType), ContractType: string(contract.ContractType),
ContractTypeName: contract.GetContractTypeName(), ContractTypeName: contract.GetContractTypeName(),
ContractFileURL: contract.ContractFileURL, ContractFileURL: contract.ContractFileURL,
CreatedAt: contract.CreatedAt, CreatedAt: contract.CreatedAt,
}) })
} }
item.EnterpriseInfo.Contracts = contractItems item.EnterpriseInfo.Contracts = contractItems

View File

@@ -1,6 +1,8 @@
package config package config
import ( import (
"os"
"strings"
"time" "time"
) )
@@ -39,6 +41,12 @@ type Config struct {
Alicloud AlicloudConfig `mapstructure:"alicloud"` Alicloud AlicloudConfig `mapstructure:"alicloud"`
Xingwei XingweiConfig `mapstructure:"xingwei"` Xingwei XingweiConfig `mapstructure:"xingwei"`
Jiguang JiguangConfig `mapstructure:"jiguang"` Jiguang JiguangConfig `mapstructure:"jiguang"`
Shumai ShumaiConfig `mapstructure:"shumai"`
Shujubao ShujubaoConfig `mapstructure:"shujubao"`
PDFGen PDFGenConfig `mapstructure:"pdfgen"`
Huibo HuiboConfig `mapstructure:"huibo"`
Nuoer NuoerConfig `mapstructure:"nuoer"`
Haiyuapi HaiyuapiConfig `mapstructure:"haiyuapi"`
} }
// ServerConfig HTTP服务器配置 // ServerConfig HTTP服务器配置
@@ -192,11 +200,44 @@ type AppConfig struct {
Name string `mapstructure:"name"` Name string `mapstructure:"name"`
Version string `mapstructure:"version"` Version string `mapstructure:"version"`
Env string `mapstructure:"env"` Env string `mapstructure:"env"`
// SubPortalBaseURL 子账号使用的前端基址(可与主站同域),用于邀请链接,无尾斜杠
SubPortalBaseURL string `mapstructure:"sub_portal_base_url"`
} }
// APIConfig API配置 // APIConfig API配置
type APIConfig struct { type APIConfig struct {
Domain string `mapstructure:"domain"` Domain string `mapstructure:"domain"`
// PublicBaseURL 浏览器/第三方访问本 API 服务的完整基址(如 https://api.example.com 或 http://127.0.0.1:8080无尾斜杠。
// 用于企业全景报告 reportUrl、headless PDF 预生成等。为空时由 Domain 推导为 https://{Domain}Domain 若已含 scheme 则沿用)。
PublicBaseURL string `mapstructure:"public_base_url"`
}
// ResolvedPublicBaseURL 由配置推导对外基址(不读环境变量)。
func (c *APIConfig) ResolvedPublicBaseURL() string {
u := strings.TrimSpace(c.PublicBaseURL)
if u != "" {
return strings.TrimRight(u, "/")
}
d := strings.TrimSpace(c.Domain)
if d == "" {
return ""
}
lo := strings.ToLower(d)
if strings.HasPrefix(lo, "http://") || strings.HasPrefix(lo, "https://") {
return strings.TrimRight(d, "/")
}
return "https://" + strings.TrimRight(d, "/")
}
// ResolveAPIPublicBaseURL 对外 API 基址。优先环境变量 API_PUBLIC_BASE_URL否则使用 API 配置。
func ResolveAPIPublicBaseURL(cfg *APIConfig) string {
if s := strings.TrimSpace(os.Getenv("API_PUBLIC_BASE_URL")); s != "" {
return strings.TrimRight(s, "/")
}
if cfg == nil {
return ""
}
return cfg.ResolvedPublicBaseURL()
} }
// SMSConfig 短信配置 // SMSConfig 短信配置
@@ -210,6 +251,14 @@ type SMSConfig struct {
ExpireTime time.Duration `mapstructure:"expire_time"` ExpireTime time.Duration `mapstructure:"expire_time"`
RateLimit SMSRateLimit `mapstructure:"rate_limit"` RateLimit SMSRateLimit `mapstructure:"rate_limit"`
MockEnabled bool `mapstructure:"mock_enabled"` // 是否启用模拟短信服务 MockEnabled bool `mapstructure:"mock_enabled"` // 是否启用模拟短信服务
// 签名验证配置
SignatureEnabled bool `mapstructure:"signature_enabled"` // 是否启用签名验证
SignatureSecret string `mapstructure:"signature_secret"` // 签名密钥
// 滑块验证码配置
CaptchaEnabled bool `mapstructure:"captcha_enabled"` // 是否启用滑块验证码
CaptchaSecret string `mapstructure:"captcha_secret"` // 阿里云验证码密钥
CaptchaEndpoint string `mapstructure:"captcha_endpoint"` // 阿里云验证码服务Endpoint
SceneID string `mapstructure:"scene_id"` // 阿里云验证码场景ID
} }
// SMSRateLimit 短信限流配置 // SMSRateLimit 短信限流配置
@@ -320,11 +369,13 @@ type SignConfig struct {
// WalletConfig 钱包配置 // WalletConfig 钱包配置
type WalletConfig struct { type WalletConfig struct {
DefaultCreditLimit float64 `mapstructure:"default_credit_limit"` DefaultCreditLimit float64 `mapstructure:"default_credit_limit"`
MinAmount string `mapstructure:"min_amount"` // 最低充值金额 MinAmount string `mapstructure:"min_amount"` // 最低充值金额
MaxAmount string `mapstructure:"max_amount"` // 最高充值金额 MaxAmount string `mapstructure:"max_amount"` // 最高充值金额
AliPayRechargeBonus []AliPayRechargeBonusRule `mapstructure:"alipay_recharge_bonus"` RechargeBonusEnabled bool `mapstructure:"recharge_bonus_enabled"` // 是否启用充值赠送,关闭后仅展示商务洽谈提示
BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"` ApiStoreRechargeTip string `mapstructure:"api_store_recharge_tip"` // API 商店充值提示文案(大额/批量需求联系商务)
AliPayRechargeBonus []AliPayRechargeBonusRule `mapstructure:"alipay_recharge_bonus"`
BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"`
} }
// BalanceAlertConfig 余额预警配置 // BalanceAlertConfig 余额预警配置
@@ -550,6 +601,168 @@ type JiguangLevelFileConfig struct {
Compress bool `mapstructure:"compress"` Compress bool `mapstructure:"compress"`
} }
// ShumaiConfig 数脉配置
type ShumaiConfig struct {
URL string `mapstructure:"url"`
AppID string `mapstructure:"app_id"`
AppSecret string `mapstructure:"app_secret"`
AppID2 string `mapstructure:"app_id2"` // 走政务接口使用这个
AppSecret2 string `mapstructure:"app_secret2"` // 走政务接口使用这个
SignMethod string `mapstructure:"sign_method"` // md5 或 hmac默认 hmac
Timeout time.Duration `mapstructure:"timeout"`
Logging ShumaiLoggingConfig `mapstructure:"logging"`
}
// ShumaiLoggingConfig 数脉日志配置
type ShumaiLoggingConfig struct {
Enabled bool `mapstructure:"enabled"`
LogDir string `mapstructure:"log_dir"`
UseDaily bool `mapstructure:"use_daily"`
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
LevelConfigs map[string]ShumaiLevelFileConfig `mapstructure:"level_configs"`
}
// ShumaiLevelFileConfig 数脉级别文件配置
type ShumaiLevelFileConfig struct {
MaxSize int `mapstructure:"max_size"`
MaxBackups int `mapstructure:"max_backups"`
MaxAge int `mapstructure:"max_age"`
Compress bool `mapstructure:"compress"`
}
// ShujubaoConfig 数据宝配置
type ShujubaoConfig struct {
URL string `mapstructure:"url"`
AppSecret string `mapstructure:"app_secret"`
SignMethod string `mapstructure:"sign_method"` // md5 或 hmac默认 hmac
Timeout time.Duration `mapstructure:"timeout"`
Logging ShujubaoLoggingConfig `mapstructure:"logging"`
}
// ShujubaoLoggingConfig 数据宝日志配置
type ShujubaoLoggingConfig struct {
Enabled bool `mapstructure:"enabled"`
LogDir string `mapstructure:"log_dir"`
UseDaily bool `mapstructure:"use_daily"`
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
LevelConfigs map[string]ShujubaoLevelFileConfig `mapstructure:"level_configs"`
}
// ShujubaoLevelFileConfig 数据宝级别文件配置
type ShujubaoLevelFileConfig struct {
MaxSize int `mapstructure:"max_size"`
MaxBackups int `mapstructure:"max_backups"`
MaxAge int `mapstructure:"max_age"`
Compress bool `mapstructure:"compress"`
}
// PDFGenConfig PDF生成服务配置
type PDFGenConfig struct {
DevelopmentURL string `mapstructure:"development_url"` // 开发环境服务地址
ProductionURL string `mapstructure:"production_url"` // 生产环境服务地址
APIPath string `mapstructure:"api_path"` // API路径
Timeout time.Duration `mapstructure:"timeout"` // 请求超时时间
Cache PDFGenCacheConfig `mapstructure:"cache"` // 缓存配置
}
// PDFGenCacheConfig PDF生成缓存配置
type PDFGenCacheConfig struct {
TTL time.Duration `mapstructure:"ttl"` // 缓存过期时间
CacheDir string `mapstructure:"cache_dir"` // 缓存目录(空则使用默认目录)
MaxSize int64 `mapstructure:"max_size"` // 最大缓存大小0表示不限制单位字节
}
// HuiboConfig 汇博BHSC配置
type HuiboConfig struct {
URL string `mapstructure:"url"`
AppID string `mapstructure:"app_id"`
AppKey string `mapstructure:"app_key"`
XOrderCode string `mapstructure:"x_order_code"`
SecretID string `mapstructure:"secret_id"`
AESKey string `mapstructure:"aes_key"`
WorkOrderCode string `mapstructure:"work_order_code"`
ProductCode string `mapstructure:"product_code"`
BaseURL2 string `mapstructure:"baseUrl2"`
AppCode2 string `mapstructure:"app_code2"`
Logging HuiboLoggingConfig `mapstructure:"logging"`
}
// HuiboLoggingConfig 汇博日志配置
type HuiboLoggingConfig struct {
Enabled bool `mapstructure:"enabled"`
LogDir string `mapstructure:"log_dir"`
UseDaily bool `mapstructure:"use_daily"`
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
LevelConfigs map[string]HuiboLevelFileConfig `mapstructure:"level_configs"`
}
// HuiboLevelFileConfig 汇博级别日志配置
type HuiboLevelFileConfig struct {
MaxSize int `mapstructure:"max_size"`
MaxBackups int `mapstructure:"max_backups"`
MaxAge int `mapstructure:"max_age"`
Compress bool `mapstructure:"compress"`
}
// NuoerConfig 诺尔智汇配置
type NuoerConfig struct {
URL string `mapstructure:"url"`
AppID string `mapstructure:"app_id"`
AppSecret string `mapstructure:"app_secret"`
Timeout time.Duration `mapstructure:"timeout"`
Logging NuoerLoggingConfig `mapstructure:"logging"`
}
// NuoerLoggingConfig 诺尔智汇日志配置
type NuoerLoggingConfig struct {
Enabled bool `mapstructure:"enabled"`
LogDir string `mapstructure:"log_dir"`
ServiceName string `mapstructure:"service_name"`
UseDaily bool `mapstructure:"use_daily"`
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
LevelConfigs map[string]NuoerLevelFileConfig `mapstructure:"level_configs"`
}
// NuoerLevelFileConfig 诺尔智汇级别文件配置
type NuoerLevelFileConfig struct {
MaxSize int `mapstructure:"max_size"`
MaxBackups int `mapstructure:"max_backups"`
MaxAge int `mapstructure:"max_age"`
Compress bool `mapstructure:"compress"`
}
// HaiyuapiConfig 海宇API上游数据源配置
type HaiyuapiConfig struct {
BaseURL string `mapstructure:"base_url"`
AccessID string `mapstructure:"access_id"`
SecretKey string `mapstructure:"secret_key"`
Timeout time.Duration `mapstructure:"timeout"`
Logging HaiyuapiLoggingConfig `mapstructure:"logging"`
}
// HaiyuapiLoggingConfig 海宇API日志配置
type HaiyuapiLoggingConfig struct {
Enabled bool `mapstructure:"enabled"`
LogDir string `mapstructure:"log_dir"`
ServiceName string `mapstructure:"service_name"`
UseDaily bool `mapstructure:"use_daily"`
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
LevelConfigs map[string]HaiyuapiLevelFileConfig `mapstructure:"level_configs"`
}
// HaiyuapiLevelFileConfig 海宇API级别文件配置
type HaiyuapiLevelFileConfig struct {
MaxSize int `mapstructure:"max_size"`
MaxBackups int `mapstructure:"max_backups"`
MaxAge int `mapstructure:"max_age"`
Compress bool `mapstructure:"compress"`
}
// DomainConfig 域名配置 // DomainConfig 域名配置
type DomainConfig struct { type DomainConfig struct {
API string `mapstructure:"api"` // API域名 API string `mapstructure:"api"` // API域名

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"strconv"
"time" "time"
"go.uber.org/fx" "go.uber.org/fx"
@@ -17,6 +16,7 @@ import (
"tyapi-server/internal/application/finance" "tyapi-server/internal/application/finance"
"tyapi-server/internal/application/product" "tyapi-server/internal/application/product"
"tyapi-server/internal/application/statistics" "tyapi-server/internal/application/statistics"
subordinate_app "tyapi-server/internal/application/subordinate"
"tyapi-server/internal/application/user" "tyapi-server/internal/application/user"
"tyapi-server/internal/config" "tyapi-server/internal/config"
api_repositories "tyapi-server/internal/domains/api/repositories" api_repositories "tyapi-server/internal/domains/api/repositories"
@@ -29,6 +29,7 @@ import (
domain_product_repo "tyapi-server/internal/domains/product/repositories" domain_product_repo "tyapi-server/internal/domains/product/repositories"
product_service "tyapi-server/internal/domains/product/services" product_service "tyapi-server/internal/domains/product/services"
statistics_service "tyapi-server/internal/domains/statistics/services" statistics_service "tyapi-server/internal/domains/statistics/services"
domain_subordinate_repo "tyapi-server/internal/domains/subordinate/repositories"
user_service "tyapi-server/internal/domains/user/services" user_service "tyapi-server/internal/domains/user/services"
"tyapi-server/internal/infrastructure/cache" "tyapi-server/internal/infrastructure/cache"
"tyapi-server/internal/infrastructure/database" "tyapi-server/internal/infrastructure/database"
@@ -36,14 +37,21 @@ import (
certification_repo "tyapi-server/internal/infrastructure/database/repositories/certification" certification_repo "tyapi-server/internal/infrastructure/database/repositories/certification"
finance_repo "tyapi-server/internal/infrastructure/database/repositories/finance" finance_repo "tyapi-server/internal/infrastructure/database/repositories/finance"
product_repo "tyapi-server/internal/infrastructure/database/repositories/product" product_repo "tyapi-server/internal/infrastructure/database/repositories/product"
subordinate_db "tyapi-server/internal/infrastructure/database/repositories/subordinate"
infra_events "tyapi-server/internal/infrastructure/events" infra_events "tyapi-server/internal/infrastructure/events"
"tyapi-server/internal/infrastructure/external/alicloud" "tyapi-server/internal/infrastructure/external/alicloud"
"tyapi-server/internal/infrastructure/external/captcha"
"tyapi-server/internal/infrastructure/external/email" "tyapi-server/internal/infrastructure/external/email"
"tyapi-server/internal/infrastructure/external/huibo"
"tyapi-server/internal/infrastructure/external/jiguang" "tyapi-server/internal/infrastructure/external/jiguang"
"tyapi-server/internal/infrastructure/external/muzi" "tyapi-server/internal/infrastructure/external/muzi"
"tyapi-server/internal/infrastructure/external/nuoer"
"tyapi-server/internal/infrastructure/external/ocr" "tyapi-server/internal/infrastructure/external/ocr"
"tyapi-server/internal/infrastructure/external/shujubao"
"tyapi-server/internal/infrastructure/external/shumai"
"tyapi-server/internal/infrastructure/external/sms" "tyapi-server/internal/infrastructure/external/sms"
"tyapi-server/internal/infrastructure/external/storage" "tyapi-server/internal/infrastructure/external/storage"
"tyapi-server/internal/infrastructure/external/haiyuapi"
"tyapi-server/internal/infrastructure/external/tianyancha" "tyapi-server/internal/infrastructure/external/tianyancha"
"tyapi-server/internal/infrastructure/external/westdex" "tyapi-server/internal/infrastructure/external/westdex"
"tyapi-server/internal/infrastructure/external/xingwei" "tyapi-server/internal/infrastructure/external/xingwei"
@@ -51,6 +59,7 @@ import (
"tyapi-server/internal/infrastructure/external/zhicha" "tyapi-server/internal/infrastructure/external/zhicha"
"tyapi-server/internal/infrastructure/http/handlers" "tyapi-server/internal/infrastructure/http/handlers"
"tyapi-server/internal/infrastructure/http/routes" "tyapi-server/internal/infrastructure/http/routes"
subordinate_infra "tyapi-server/internal/infrastructure/subordinate"
"tyapi-server/internal/infrastructure/task" "tyapi-server/internal/infrastructure/task"
task_implementations "tyapi-server/internal/infrastructure/task/implementations" task_implementations "tyapi-server/internal/infrastructure/task/implementations"
asynq "tyapi-server/internal/infrastructure/task/implementations/asynq" asynq "tyapi-server/internal/infrastructure/task/implementations/asynq"
@@ -65,6 +74,7 @@ import (
"tyapi-server/internal/shared/hooks" "tyapi-server/internal/shared/hooks"
sharedhttp "tyapi-server/internal/shared/http" sharedhttp "tyapi-server/internal/shared/http"
"tyapi-server/internal/shared/interfaces" "tyapi-server/internal/shared/interfaces"
"tyapi-server/internal/shared/ipgeo"
"tyapi-server/internal/shared/logger" "tyapi-server/internal/shared/logger"
"tyapi-server/internal/shared/metrics" "tyapi-server/internal/shared/metrics"
"tyapi-server/internal/shared/middleware" "tyapi-server/internal/shared/middleware"
@@ -85,6 +95,7 @@ import (
api_app "tyapi-server/internal/application/api" api_app "tyapi-server/internal/application/api"
domain_api_repo "tyapi-server/internal/domains/api/repositories" domain_api_repo "tyapi-server/internal/domains/api/repositories"
api_services "tyapi-server/internal/domains/api/services" api_services "tyapi-server/internal/domains/api/services"
api_processors "tyapi-server/internal/domains/api/services/processors"
finance_services "tyapi-server/internal/domains/finance/services" finance_services "tyapi-server/internal/domains/finance/services"
product_services "tyapi-server/internal/domains/product/services" product_services "tyapi-server/internal/domains/product/services"
domain_statistics_repo "tyapi-server/internal/domains/statistics/repositories" domain_statistics_repo "tyapi-server/internal/domains/statistics/repositories"
@@ -237,6 +248,19 @@ func NewContainer() *Container {
}, },
// 短信服务 // 短信服务
sms.NewAliSMSService, sms.NewAliSMSService,
// 验证码服务
fx.Annotate(
func(cfg *config.Config) *captcha.CaptchaService {
return captcha.NewCaptchaService(captcha.CaptchaConfig{
AccessKeyID: cfg.SMS.AccessKeyID,
AccessKeySecret: cfg.SMS.AccessKeySecret,
EndpointURL: cfg.SMS.CaptchaEndpoint,
SceneID: cfg.SMS.SceneID,
EncryptKey: cfg.SMS.CaptchaSecret, // 加密模式 ekeyBase64 编码的 32 字节)
})
},
fx.ResultTags(`name:"captchaService"`),
),
// 邮件服务 // 邮件服务
fx.Annotate( fx.Annotate(
func(cfg *config.Config, logger *zap.Logger) *email.QQEmailService { func(cfg *config.Config, logger *zap.Logger) *email.QQEmailService {
@@ -371,6 +395,26 @@ func NewContainer() *Container {
func(cfg *config.Config) (*jiguang.JiguangService, error) { func(cfg *config.Config) (*jiguang.JiguangService, error) {
return jiguang.NewJiguangServiceWithConfig(cfg) return jiguang.NewJiguangServiceWithConfig(cfg)
}, },
// ShumaiService - 数脉服务
func(cfg *config.Config) (*shumai.ShumaiService, error) {
return shumai.NewShumaiServiceWithConfig(cfg)
},
// HuiboService - 汇博(BHSC)服务
func(cfg *config.Config) (*huibo.HuiboService, error) {
return huibo.NewHuiboServiceWithConfig(cfg)
},
// ShujubaoService - 数据宝服务
func(cfg *config.Config) (*shujubao.ShujubaoService, error) {
return shujubao.NewShujubaoServiceWithConfig(cfg)
},
// NuoerService - 诺尔智汇服务
func(cfg *config.Config) (*nuoer.NuoerService, error) {
return nuoer.NewNuoerServiceWithConfig(cfg)
},
// HaiyuapiService - 海宇API上游服务
func(cfg *config.Config) (*haiyuapi.HaiyuapiService, error) {
return haiyuapi.NewHaiyuapiServiceWithConfig(cfg)
},
func(cfg *config.Config) *yushan.YushanService { func(cfg *config.Config) *yushan.YushanService {
return yushan.NewYushanService( return yushan.NewYushanService(
cfg.Yushan.URL, cfg.Yushan.URL,
@@ -388,13 +432,11 @@ func NewContainer() *Container {
) )
}, },
// AlicloudService - 阿里云服务 // AlicloudService - 阿里云服务
func(cfg *config.Config) *alicloud.AlicloudService { func(cfg *config.Config) (*alicloud.AlicloudService, error) {
return alicloud.NewAlicloudService( return alicloud.NewAlicloudServiceWithConfig(cfg)
cfg.Alicloud.Host,
cfg.Alicloud.AppCode,
)
}, },
sharedhttp.NewGinRouter, sharedhttp.NewGinRouter,
ipgeo.NewLocator,
), ),
// 中间件组件 // 中间件组件
@@ -405,7 +447,7 @@ func NewContainer() *Container {
middleware.NewCORSMiddleware, middleware.NewCORSMiddleware,
middleware.NewRateLimitMiddleware, middleware.NewRateLimitMiddleware,
// 每日限流中间件 // 每日限流中间件
func(cfg *config.Config, redis *redis.Client, response interfaces.ResponseBuilder, logger *zap.Logger) *middleware.DailyRateLimitMiddleware { func(cfg *config.Config, redis *redis.Client, db *gorm.DB, response interfaces.ResponseBuilder, logger *zap.Logger) *middleware.DailyRateLimitMiddleware {
limitConfig := middleware.DailyRateLimitConfig{ limitConfig := middleware.DailyRateLimitConfig{
MaxRequestsPerDay: cfg.DailyRateLimit.MaxRequestsPerDay, MaxRequestsPerDay: cfg.DailyRateLimit.MaxRequestsPerDay,
MaxRequestsPerIP: cfg.DailyRateLimit.MaxRequestsPerIP, MaxRequestsPerIP: cfg.DailyRateLimit.MaxRequestsPerIP,
@@ -429,7 +471,7 @@ func NewContainer() *Container {
// 排除域名配置 // 排除域名配置
ExcludeDomains: cfg.DailyRateLimit.ExcludeDomains, ExcludeDomains: cfg.DailyRateLimit.ExcludeDomains,
} }
return middleware.NewDailyRateLimitMiddleware(cfg, redis, response, logger, limitConfig) return middleware.NewDailyRateLimitMiddleware(cfg, redis, db, response, logger, limitConfig)
}, },
NewRequestLoggerMiddlewareWrapper, NewRequestLoggerMiddlewareWrapper,
middleware.NewJWTAuthMiddleware, middleware.NewJWTAuthMiddleware,
@@ -637,6 +679,18 @@ func NewContainer() *Container {
api_repo.NewGormApiCallRepository, api_repo.NewGormApiCallRepository,
fx.As(new(domain_api_repo.ApiCallRepository)), fx.As(new(domain_api_repo.ApiCallRepository)),
), ),
fx.Annotate(
api_repo.NewGormReportRepository,
fx.As(new(domain_api_repo.ReportRepository)),
),
),
// 下属账号仓储
fx.Provide(
fx.Annotate(
subordinate_db.NewGormSubordinateRepository,
fx.As(new(domain_subordinate_repo.SubordinateRepository)),
),
), ),
// 统计域仓储层 // 统计域仓储层
@@ -661,7 +715,10 @@ func NewContainer() *Container {
user_service.NewUserAggregateService, user_service.NewUserAggregateService,
), ),
user_service.NewUserAuthService, user_service.NewUserAuthService,
user_service.NewSMSCodeService, fx.Annotate(
user_service.NewSMSCodeService,
fx.ParamTags(``, ``, ``, `name:"captchaService"`),
),
user_service.NewContractAggregateService, user_service.NewContractAggregateService,
product_service.NewProductManagementService, product_service.NewProductManagementService,
product_service.NewProductSubscriptionService, product_service.NewProductSubscriptionService,
@@ -743,7 +800,8 @@ func NewContainer() *Container {
api_services.NewApiUserAggregateService, api_services.NewApiUserAggregateService,
), ),
api_services.NewApiCallAggregateService, api_services.NewApiCallAggregateService,
api_services.NewApiRequestService, // 使用带仓储注入的构造函数,支持企业报告记录持久化
api_services.NewApiRequestServiceWithRepos,
api_services.NewFormConfigService, api_services.NewFormConfigService,
), ),
@@ -768,6 +826,7 @@ func NewContainer() *Container {
subscriptionService *product_services.ProductSubscriptionService, subscriptionService *product_services.ProductSubscriptionService,
exportManager *export.ExportManager, exportManager *export.ExportManager,
balanceAlertService finance_services.BalanceAlertService, balanceAlertService finance_services.BalanceAlertService,
subordinateRepo domain_subordinate_repo.SubordinateRepository,
) api_app.ApiApplicationService { ) api_app.ApiApplicationService {
return api_app.NewApiApplicationService( return api_app.NewApiApplicationService(
apiCallService, apiCallService,
@@ -786,6 +845,7 @@ func NewContainer() *Container {
subscriptionService, subscriptionService,
exportManager, exportManager,
balanceAlertService, balanceAlertService,
subordinateRepo,
) )
}, },
fx.As(new(api_app.ApiApplicationService)), fx.As(new(api_app.ApiApplicationService)),
@@ -858,6 +918,21 @@ func NewContainer() *Container {
user.NewUserApplicationService, user.NewUserApplicationService,
fx.As(new(user.UserApplicationService)), fx.As(new(user.UserApplicationService)),
), ),
// 下属:账号类型供 JWT / 资料
fx.Annotate(
subordinate_infra.NewAccountKindProviderImpl,
fx.As(new(interfaces.AccountKindProvider)),
),
// 下属:禁止子账号自助订
fx.Annotate(
subordinate_app.NewBlockSelfSubscribeForSubordinate,
fx.As(new(product.SelfSubscribePolicy)),
),
// 下属:邀请/划款/代配
fx.Annotate(
subordinate_app.NewSubordinateApplicationService,
fx.As(new(subordinate_app.SubordinateApplicationService)),
),
// 认证应用服务 - 绑定到接口 // 认证应用服务 - 绑定到接口
fx.Annotate( fx.Annotate(
func( func(
@@ -874,8 +949,11 @@ func NewContainer() *Container {
apiUserAggregateService api_services.ApiUserAggregateService, apiUserAggregateService api_services.ApiUserAggregateService,
enterpriseInfoSubmitRecordService *certification_service.EnterpriseInfoSubmitRecordService, enterpriseInfoSubmitRecordService *certification_service.EnterpriseInfoSubmitRecordService,
ocrService sharedOCR.OCRService, ocrService sharedOCR.OCRService,
subordinateRepo domain_subordinate_repo.SubordinateRepository,
walletRepo domain_finance_repo.WalletRepository,
txManager *shared_database.TransactionManager, txManager *shared_database.TransactionManager,
logger *zap.Logger, logger *zap.Logger,
cfg *config.Config,
) certification.CertificationApplicationService { ) certification.CertificationApplicationService {
return certification.NewCertificationApplicationService( return certification.NewCertificationApplicationService(
aggregateService, aggregateService,
@@ -891,8 +969,11 @@ func NewContainer() *Container {
apiUserAggregateService, apiUserAggregateService,
enterpriseInfoSubmitRecordService, enterpriseInfoSubmitRecordService,
ocrService, ocrService,
subordinateRepo,
walletRepo,
txManager, txManager,
logger, logger,
cfg,
) )
}, },
fx.As(new(certification.CertificationApplicationService)), fx.As(new(certification.CertificationApplicationService)),
@@ -955,6 +1036,7 @@ func NewContainer() *Container {
documentationAppService product.DocumentationApplicationServiceInterface, documentationAppService product.DocumentationApplicationServiceInterface,
formConfigService api_services.FormConfigService, formConfigService api_services.FormConfigService,
logger *zap.Logger, logger *zap.Logger,
exportManager *export.ExportManager,
) product.ProductApplicationService { ) product.ProductApplicationService {
return product.NewProductApplicationService( return product.NewProductApplicationService(
productManagementService, productManagementService,
@@ -963,6 +1045,7 @@ func NewContainer() *Container {
documentationAppService, documentationAppService,
formConfigService, formConfigService,
logger, logger,
exportManager,
) )
}, },
fx.As(new(product.ProductApplicationService)), fx.As(new(product.ProductApplicationService)),
@@ -1140,36 +1223,33 @@ func NewContainer() *Container {
return pdf.NewPDFGenerator(logger) return pdf.NewPDFGenerator(logger)
}, },
), ),
// PDF缓存管理器 // PDF缓存管理器用于PDFG
fx.Provide( fx.Provide(
func(logger *zap.Logger) (*pdf.PDFCacheManager, error) { func(cfg *config.Config, logger *zap.Logger) (*pdf.PDFCacheManager, error) {
// 使用默认配置缓存目录在临时目录TTL为24小时最大缓存大小为500MB cacheDir := cfg.PDFGen.Cache.CacheDir
cacheDir := "" // 使用默认目录临时目录下的tyapi_pdf_cache ttl := cfg.PDFGen.Cache.TTL
ttl := 24 * time.Hour if ttl == 0 {
maxSize := int64(500 * 1024 * 1024) // 500MB ttl = 24 * time.Hour
}
// 可以通过环境变量覆盖 // 环境变量可以覆盖配置
if envCacheDir := os.Getenv("PDF_CACHE_DIR"); envCacheDir != "" { if envCacheDir := os.Getenv("PDFG_CACHE_DIR"); envCacheDir != "" {
cacheDir = envCacheDir cacheDir = envCacheDir
} }
if envTTL := os.Getenv("PDF_CACHE_TTL"); envTTL != "" { if envTTL := os.Getenv("PDFG_CACHE_TTL"); envTTL != "" {
if parsedTTL, err := time.ParseDuration(envTTL); err == nil { if parsedTTL, err := time.ParseDuration(envTTL); err == nil {
ttl = parsedTTL ttl = parsedTTL
} }
} }
if envMaxSize := os.Getenv("PDF_CACHE_MAX_SIZE"); envMaxSize != "" {
if parsedMaxSize, err := strconv.ParseInt(envMaxSize, 10, 64); err == nil {
maxSize = parsedMaxSize
}
}
maxSize := cfg.PDFGen.Cache.MaxSize
cacheManager, err := pdf.NewPDFCacheManager(logger, cacheDir, ttl, maxSize) cacheManager, err := pdf.NewPDFCacheManager(logger, cacheDir, ttl, maxSize)
if err != nil { if err != nil {
logger.Warn("PDF缓存管理器初始化失败,将禁用缓存功能", zap.Error(err)) logger.Warn("PDFG缓存管理器初始化失败", zap.Error(err))
return nil, nil // 返回nilhandler中会检查 return nil, err
} }
logger.Info("PDF缓存管理器已初始化", logger.Info("PDFG缓存管理器已初始化",
zap.String("cache_dir", cacheDir), zap.String("cache_dir", cacheDir),
zap.Duration("ttl", ttl), zap.Duration("ttl", ttl),
zap.Int64("max_size", maxSize), zap.Int64("max_size", maxSize),
@@ -1178,6 +1258,18 @@ func NewContainer() *Container {
return cacheManager, nil return cacheManager, nil
}, },
), ),
// 企业全景报告 PDF 异步预生成(依赖 PDF 缓存目录与公网可访问基址)
// 同时以 processors.QYGLReportPDFScheduler 注入 ApiRequestService
fx.Provide(
fx.Annotate(
func(cfg *config.Config, logger *zap.Logger, cache *pdf.PDFCacheManager) *pdf.QYGLReportPDFPregen {
base := config.ResolveAPIPublicBaseURL(&cfg.API)
return pdf.NewQYGLReportPDFPregen(logger, cache, base)
},
fx.As(new(api_processors.QYGLReportPDFScheduler)),
fx.As(fx.Self()), // 同时保留 *pdf.QYGLReportPDFPregen供 QYGLReportHandler 等注入
),
),
// 本地文件存储服务 // 本地文件存储服务
fx.Provide( fx.Provide(
func(logger *zap.Logger) *storage.LocalFileStorageService { func(logger *zap.Logger) *storage.LocalFileStorageService {
@@ -1200,6 +1292,7 @@ func NewContainer() *Container {
fx.Provide( fx.Provide(
// 用户HTTP处理器 // 用户HTTP处理器
handlers.NewUserHandler, handlers.NewUserHandler,
handlers.NewSubordinateHandler,
// 认证HTTP处理器 // 认证HTTP处理器
handlers.NewCertificationHandler, handlers.NewCertificationHandler,
// 财务HTTP处理器 // 财务HTTP处理器
@@ -1214,6 +1307,8 @@ func NewContainer() *Container {
handlers.NewApiHandler, handlers.NewApiHandler,
// 统计HTTP处理器 // 统计HTTP处理器
handlers.NewStatisticsHandler, handlers.NewStatisticsHandler,
// 管理员安全HTTP处理器
handlers.NewAdminSecurityHandler,
// 文章HTTP处理器 // 文章HTTP处理器
func( func(
appService article.ArticleApplicationService, appService article.ArticleApplicationService,
@@ -1232,6 +1327,8 @@ func NewContainer() *Container {
) *handlers.AnnouncementHandler { ) *handlers.AnnouncementHandler {
return handlers.NewAnnouncementHandler(appService, responseBuilder, validator, logger) return handlers.NewAnnouncementHandler(appService, responseBuilder, validator, logger)
}, },
// PDFG HTTP处理器
handlers.NewPDFGHandler,
// 组件报告处理器 // 组件报告处理器
func( func(
productRepo domain_product_repo.ProductRepository, productRepo domain_product_repo.ProductRepository,
@@ -1257,6 +1354,8 @@ func NewContainer() *Container {
) *handlers.ComponentReportOrderHandler { ) *handlers.ComponentReportOrderHandler {
return handlers.NewComponentReportOrderHandler(componentReportOrderService, purchaseOrderRepo, config, logger) return handlers.NewComponentReportOrderHandler(componentReportOrderService, purchaseOrderRepo, config, logger)
}, },
// 企业全景报告页面处理器
handlers.NewQYGLReportHandler,
// UI组件HTTP处理器 // UI组件HTTP处理器
func( func(
uiComponentAppService product.UIComponentApplicationService, uiComponentAppService product.UIComponentApplicationService,
@@ -1266,12 +1365,20 @@ func NewContainer() *Container {
) *handlers.UIComponentHandler { ) *handlers.UIComponentHandler {
return handlers.NewUIComponentHandler(uiComponentAppService, responseBuilder, validator, logger) return handlers.NewUIComponentHandler(uiComponentAppService, responseBuilder, validator, logger)
}, },
// 验证码HTTP处理器
fx.Annotate(
handlers.NewCaptchaHandler,
fx.ParamTags(`name:"captchaService"`, ``, ``, ``),
),
), ),
// 路由注册 // 路由注册
fx.Provide( fx.Provide(
// 用户路由 // 用户路由
routes.NewUserRoutes, routes.NewUserRoutes,
routes.NewSubordinateRoutes,
// 验证码路由
routes.NewCaptchaRoutes,
// 认证路由 // 认证路由
routes.NewCertificationRoutes, routes.NewCertificationRoutes,
// 财务路由 // 财务路由
@@ -1294,6 +1401,12 @@ func NewContainer() *Container {
routes.NewApiRoutes, routes.NewApiRoutes,
// 统计路由 // 统计路由
routes.NewStatisticsRoutes, routes.NewStatisticsRoutes,
// 管理员安全路由
routes.NewAdminSecurityRoutes,
// PDFG路由
routes.NewPDFGRoutes,
// 企业报告页面路由
routes.NewQYGLReportRoutes,
), ),
// 应用生命周期 // 应用生命周期
@@ -1396,6 +1509,8 @@ func RegisterMiddlewares(
func RegisterRoutes( func RegisterRoutes(
router *sharedhttp.GinRouter, router *sharedhttp.GinRouter,
userRoutes *routes.UserRoutes, userRoutes *routes.UserRoutes,
subordinateRoutes *routes.SubordinateRoutes,
captchaRoutes *routes.CaptchaRoutes,
certificationRoutes *routes.CertificationRoutes, certificationRoutes *routes.CertificationRoutes,
financeRoutes *routes.FinanceRoutes, financeRoutes *routes.FinanceRoutes,
productRoutes *routes.ProductRoutes, productRoutes *routes.ProductRoutes,
@@ -1407,6 +1522,9 @@ func RegisterRoutes(
announcementRoutes *routes.AnnouncementRoutes, announcementRoutes *routes.AnnouncementRoutes,
apiRoutes *routes.ApiRoutes, apiRoutes *routes.ApiRoutes,
statisticsRoutes *routes.StatisticsRoutes, statisticsRoutes *routes.StatisticsRoutes,
adminSecurityRoutes *routes.AdminSecurityRoutes,
pdfgRoutes *routes.PDFGRoutes,
qyglReportRoutes *routes.QYGLReportRoutes,
jwtAuth *middleware.JWTAuthMiddleware, jwtAuth *middleware.JWTAuthMiddleware,
adminAuth *middleware.AdminAuthMiddleware, adminAuth *middleware.AdminAuthMiddleware,
cfg *config.Config, cfg *config.Config,
@@ -1419,6 +1537,8 @@ func RegisterRoutes(
// 所有域名路由路由 // 所有域名路由路由
userRoutes.Register(router) userRoutes.Register(router)
subordinateRoutes.Register(router)
captchaRoutes.Register(router)
certificationRoutes.Register(router) certificationRoutes.Register(router)
financeRoutes.Register(router) financeRoutes.Register(router)
productRoutes.Register(router) productRoutes.Register(router)
@@ -1430,6 +1550,9 @@ func RegisterRoutes(
articleRoutes.Register(router) articleRoutes.Register(router)
announcementRoutes.Register(router) announcementRoutes.Register(router)
statisticsRoutes.Register(router) statisticsRoutes.Register(router)
adminSecurityRoutes.Register(router)
pdfgRoutes.Register(router)
qyglReportRoutes.Register(router)
// 打印注册的路由信息 // 打印注册的路由信息
router.PrintRoutes() router.PrintRoutes()

View File

@@ -72,6 +72,11 @@ type IVYZ81NCReq struct {
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
} }
type IVYZ2MN6Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
}
type IVYZ9363Req struct { type IVYZ9363Req struct {
ManName string `json:"man_name" validate:"required,min=1,validName"` ManName string `json:"man_name" validate:"required,min=1,validName"`
ManIDCard string `json:"man_id_card" validate:"required,validIDCard"` ManIDCard string `json:"man_id_card" validate:"required,validIDCard"`
@@ -100,11 +105,96 @@ type JRZQDCBEReq struct {
BankCard string `json:"bank_card" validate:"required,validBankCard"` BankCard string `json:"bank_card" validate:"required,validBankCard"`
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
} }
type JRZQACABReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
BankCard string `json:"bank_card" validate:"required,validBankCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
// shujubao
type QYGL2ACDReq struct { type QYGL2ACDReq struct {
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"` EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"` LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
EntCode string `json:"ent_code" validate:"required,validUSCI"` EntCode string `json:"ent_code" validate:"required,validUSCI"`
} }
type QYGLUY3SReq struct {
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
EntRegno string `json:"ent_reg_no" validate:"omitempty"`
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
}
type QYGLJ1U9Req struct {
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
EntCode string `json:"ent_code" validate:"required,validUSCI"`
}
type JRZQOCRYReq struct {
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
}
type JRZQOCREReq struct {
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
ImageUrl string `json:"image_url" validate:"omitempty,url"`
}
type YYSYK9R4Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type YYSY35TAReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYTJC4Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYT7C4Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type QCXG1S2LReq struct {
VinCode string `json:"vin_code" validate:"required"`
}
type YYSYXF7JReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type QCXGA8V3Req struct {
VinCode string `json:"vin_code" validate:"required"`
}
type YYSYP72DReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type QCXG9F5CReq struct {
PlateNo string `json:"plate_no" validate:"required"`
}
type QCXG3M7ZReq struct {
PlateNo string `json:"plate_no" validate:"required"`
Name string `json:"name" validate:"required,min=1,validName"`
PlateColor string `json:"plate_color" validate:"omitempty"`
}
type QCXG3B8ZReq struct {
PlateNo string `json:"plate_no" validate:"required"`
}
type QCXGM7R9Req struct {
PlateNo string `json:"plate_no" validate:"required"`
}
type QCXGP1W3Req struct {
PlateNo string `json:"plate_no" validate:"required"`
}
type QCXG5U0ZReq struct {
VinCode string `json:"vin_code" validate:"required"`
}
type QCXGU2K4Req struct {
PlateNo string `json:"plate_no" validate:"required"`
}
type QCXGY7F2Req struct {
VinCode string `json:"vin_code" validate:"required"`
VehicleName string `json:"vehicle_name" validate:"omitempty"`
VehicleLocation string `json:"vehicle_location" validate:"required"`
FirstRegistrationdate string `json:"first_registrationdate" validate:"required"`
Color string `json:"color" validate:"omitempty"`
}
type QYGL6F2DReq struct { type QYGL6F2DReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
} }
@@ -176,6 +266,13 @@ type IVYZZQT3Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
PhotoData string `json:"photo_data" validate:"required,validBase64Image"` PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
} }
type IVYZZQ3BReq struct {
Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
ImageUrl string `json:"image_url" validate:"omitempty,url"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
}
type IVYZSFELReq struct { type IVYZSFELReq struct {
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
@@ -265,9 +362,8 @@ type COMB86PMReq struct {
} }
type QCXG7A2BReq struct { type QCXG7A2BReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"` PlateNo string `json:"plate_no" validate:"required"`
} }
type QCXG4896Req struct { type QCXG4896Req struct {
PlateNo string `json:"plate_no" validate:"required"` PlateNo string `json:"plate_no" validate:"required"`
AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"` AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"`
@@ -282,6 +378,11 @@ type QCXGGB2QReq struct {
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
CarPlateType string `json:"carplate_type" validate:"required"` CarPlateType string `json:"carplate_type" validate:"required"`
} }
type QCXGM4CLReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
}
type QCXGJJ2AReq struct { type QCXGJJ2AReq struct {
VinCode string `json:"vin_code" validate:"required"` VinCode string `json:"vin_code" validate:"required"`
EngineNumber string `json:"engine_number" validate:"omitempty"` EngineNumber string `json:"engine_number" validate:"omitempty"`
@@ -346,7 +447,7 @@ type QCXGP00WReq struct {
VinCode string `json:"vin_code" validate:"required"` VinCode string `json:"vin_code" validate:"required"`
PlateNo string `json:"plate_no" validate:"omitempty"` PlateNo string `json:"plate_no" validate:"omitempty"`
ReturnURL string `json:"return_url" validate:"required,validReturnURL"` ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
VlPhotoData string `json:"vlphoto_data" validate:"omitempty,validBase64Image"` VlPhotoData string `json:"vlphoto_data" validate:"required,validBase64Image"`
} }
type QCXG4D2EReq struct { type QCXG4D2EReq struct {
@@ -359,7 +460,7 @@ type COMENT01Req struct {
} }
type JRZQ09J8Req struct { type JRZQ09J8Req struct {
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"` MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"` Authorized string `json:"authorized" validate:"required,oneof=0 1"`
@@ -412,6 +513,30 @@ type IVYZ2A8BReq struct {
Authorized string `json:"authorized" validate:"required,oneof=0 1"` Authorized string `json:"authorized" validate:"required,oneof=0 1"`
} }
type QYGL4YABReq struct {
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
EntCode string `json:"ent_code" validate:"required,validUSCI"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
}
type QYGL3YSBReq struct {
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
EntCode string `json:"ent_code" validate:"required,validUSCI"`
}
type QYGL2YSBReq struct {
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
EntCode string `json:"ent_code" validate:"required,validUSCI"`
}
type QYGLDG77Req struct {
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
AccountNo string `json:"account_no" validate:"required,min=1"`
AccountBank string `json:"account_bank" validate:"required,min=1"`
}
type IVYZ7C9DReq struct { type IVYZ7C9DReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
@@ -430,6 +555,12 @@ type IVYZ7F3AReq struct {
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"` Authorized string `json:"authorized" validate:"required,oneof=0 1"`
} }
type IVYZRAX1Req struct {
Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
}
type IVYZ3P9MReq struct { type IVYZ3P9MReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
@@ -447,6 +578,18 @@ type IVYZ9K2LReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
PhotoData string `json:"photo_data" validate:"required,validBase64Image"` PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
} }
type IVYZ4Y27Req struct {
Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
AuthAuthorizeFileBase64 string `json:"auth_authorize_file_base64" validate:"required,validBase64PDF"`
}
type FLXGHB4FReq struct {
Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
AuthAuthorizeFileBase64 string `json:"auth_authorize_file_base64" validate:"required,validBase64PDF"`
}
type IVYZP2Q6Req struct { type IVYZP2Q6Req struct {
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
@@ -485,6 +628,11 @@ type QYGL5A3CReq struct {
PageNum int64 `json:"page_num" validate:"omitempty,min=1"` PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
} }
type QYGLBH7YReq struct {
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
AuthAuthorizeFileBase64 string `json:"auth_authorize_file_base64" validate:"required,validBase64PDF"`
}
type QYGL2naoReq struct { type QYGL2naoReq struct {
EntCode string `json:"ent_code" validate:"required,validUSCI"` EntCode string `json:"ent_code" validate:"required,validUSCI"`
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"` PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
@@ -540,10 +688,67 @@ type YYSY6F2BReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
} }
type IVYZOCR1Req struct {
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
ImageUrl string `json:"image_url" validate:"omitempty,url"`
}
type YYSY8B1CReq struct { type YYSY8B1CReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
} }
type QYGLJ0Q1Req struct {
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
}
type IVYZ18HYReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MaritalType string `json:"marital_type" validate:"required" oneof=10 20 30 40`
AuthAuthorizeFileBase64 string `json:"auth_authorize_file_base64" validate:"required,validBase64"`
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
AuthDate string `json:"auth_date" validate:"omitempty"`
}
type IVYZ38SRReq struct {
ManName string `json:"man_name" validate:"required,min=1,validName"`
ManIDCard string `json:"man_id_card" validate:"required,validIDCard"`
WomanName string `json:"woman_name" validate:"required,min=1,validName"`
WomanIDCard string `json:"woman_id_card" validate:"required,validIDCard"`
}
type IVYZ5E22Req struct {
ManName string `json:"man_name" validate:"required,min=1,validName"`
ManIDCard string `json:"man_id_card" validate:"required,validIDCard"`
WomanName string `json:"woman_name" validate:"required,min=1,validName"`
WomanIDCard string `json:"woman_id_card" validate:"required,validIDCard"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
}
type IVYZ48SRReq struct {
ManName string `json:"man_name" validate:"required,min=1,validName"`
ManIDCard string `json:"man_id_card" validate:"required,validIDCard"`
WomanName string `json:"woman_name" validate:"required,min=1,validName"`
WomanIDCard string `json:"woman_id_card" validate:"required,validIDCard"`
MaritalType string `json:"marital_type" validate:"required" oneof=10 20 30 40`
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
}
type IVYZ28HYReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type FLXGDJG3Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
}
type QYGLDJ12Req struct {
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
EntRegNo string `json:"ent_reg_no" validate:"omitempty"`
}
type QYGLDJ33Req struct {
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
EntRegNo string `json:"ent_reg_no" validate:"omitempty"`
}
type YYSY6D9AReq struct { type YYSY6D9AReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
@@ -567,6 +772,13 @@ type FLXG9C1DReq struct {
Authorized string `json:"authorized" validate:"required,oneof=0 1"` Authorized string `json:"authorized" validate:"required,oneof=0 1"`
} }
type DWBG5SAMReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
AuthorizationURL string `json:"authorization_url" validate:"required,authorization_url"`
}
// 法院被执行人限高版 // 法院被执行人限高版
type FLXG3A9BReq struct { type FLXG3A9BReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
@@ -709,6 +921,24 @@ type QCXG9P1CReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
} }
type DWBG3BF9Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type DWBG9FB3Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type DWBG9FB2Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type QCXG8A3DReq struct { type QCXG8A3DReq struct {
PlateNo string `json:"plate_no" validate:"required"` PlateNo string `json:"plate_no" validate:"required"`
PlateType string `json:"plate_type" validate:"omitempty,oneof=01 02"` PlateType string `json:"plate_type" validate:"omitempty,oneof=01 02"`
@@ -829,6 +1059,10 @@ type QYGL5F6AReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
} }
type QYGLVR76Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
}
type IVYZ6G7HReq struct { type IVYZ6G7HReq struct {
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
@@ -853,3 +1087,152 @@ type IVYZ9H2MReq struct {
type YYSY9E4AReq struct { type YYSY9E4AReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
} }
// YYSY运营商相关API DTO
type YYSY3M8SReq struct {
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYC4R9Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYH6D2Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYH6F3Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYP0T4Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYE7V5Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYS9W1Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYK8R3Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYF2T7Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
DateRange string `json:"date_range" validate:"required,validDateRange"`
}
type QYGL5S1IReq struct {
EntCode string `json:"ent_code" validate:"required,validUSCI"`
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
}
// 数脉 API
type IVYZ3M8SReq struct {
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type IVYZ9K7FReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type IVYZS1MPReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type IVYZA1B3Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
}
type IVYZFIC1Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
ImageUrl string `json:"+" validate:"omitempty,url"`
}
type IVYZC4R9Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type IVYZP0T4Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type IVYZX5QZReq struct {
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
}
type IVYZX5Q2Req struct {
Token string `json:"token" validate:"required"`
}
type JRZQ1P5GReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
}
type JRZQV87MReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type JRZQV0MDReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type JRZQVT43Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type JRZQV3HMReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type JRZQV4TFReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type JRZQV5F4Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type JRZQV7MDReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type IVYZVJJ6Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}

View File

@@ -0,0 +1,10 @@
package dto
// PDFG01GZReq PDFG01GZ 请求参数
type PDFG01GZReq struct {
Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"` // 授权标识0或1
}

View File

@@ -71,9 +71,6 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
if accessId == "" { if accessId == "" {
return nil, errors.New("AccessId不能为空") return nil, errors.New("AccessId不能为空")
} }
if requestParams == "" {
return nil, errors.New("请求参数不能为空")
}
if clientIp == "" { if clientIp == "" {
return nil, errors.New("ClientIp不能为空") return nil, errors.New("ClientIp不能为空")
} }
@@ -92,11 +89,11 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
// MarkSuccess 标记为成功 // MarkSuccess 标记为成功
func (a *ApiCall) MarkSuccess(cost decimal.Decimal) error { func (a *ApiCall) MarkSuccess(cost decimal.Decimal) error {
// 校验除ErrorMsg和ErrorType外所有字段不能为空 // 校验除ErrorMsg和ErrorType外所有字段不能为空
if a.ID == "" || a.AccessId == "" || a.TransactionId == "" || a.RequestParams == "" || a.Status == "" || a.StartAt.IsZero() { if a.ID == "" || a.AccessId == "" || a.TransactionId == "" || a.Status == "" || a.StartAt.IsZero() {
return errors.New("ApiCall字段不能为空除ErrorMsg和ErrorType") return errors.New("ApiCall字段不能为空除ErrorMsg和ErrorType")
} }
// 可选字段也要有值 // 可选字段也要有值
if a.UserId == nil || a.ProductId == nil { if a.UserId == nil || a.ProductId == nil {
return errors.New("ApiCall标记成功时UserId、ProductId不能为空") return errors.New("ApiCall标记成功时UserId、ProductId不能为空")
} }
a.Status = ApiCallStatusSuccess a.Status = ApiCallStatusSuccess
@@ -132,9 +129,6 @@ func (a *ApiCall) Validate() error {
if a.TransactionId == "" { if a.TransactionId == "" {
return errors.New("TransactionId不能为空") return errors.New("TransactionId不能为空")
} }
if a.RequestParams == "" {
return errors.New("请求参数不能为空")
}
if a.Status != ApiCallStatusPending && a.Status != ApiCallStatusSuccess && a.Status != ApiCallStatusFailed { if a.Status != ApiCallStatusPending && a.Status != ApiCallStatusSuccess && a.Status != ApiCallStatusFailed {
return errors.New("无效的调用状态") return errors.New("无效的调用状态")
} }

View File

@@ -0,0 +1,35 @@
package entities
import "time"
// Report 报告记录实体
// 用于持久化存储各类报告(企业报告等),通过编号和类型区分
type Report struct {
// 报告编号,直接使用业务生成的 reportId 作为主键
ReportID string `gorm:"primaryKey;type:varchar(64)" json:"report_id"`
// 报告类型,例如 enterprise企业报告、personal 等
Type string `gorm:"type:varchar(32);not null;index" json:"type"`
// 调用来源API编码例如 QYGLJ1U9
ApiCode string `gorm:"type:varchar(32);not null;index" json:"api_code"`
// 企业名称和统一社会信用代码,便于后续检索
EntName string `gorm:"type:varchar(255);index" json:"ent_name"`
EntCode string `gorm:"type:varchar(64);index" json:"ent_code"`
// 原始请求参数JSON字符串用于审计和排错
RequestParams string `gorm:"type:text" json:"request_params"`
// 报告完整JSON内容
ReportData string `gorm:"type:text" json:"report_data"`
// 创建时间
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
}
// TableName 指定数据库表名
func (Report) TableName() string {
return "reports"
}

View File

@@ -0,0 +1,17 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/api/entities"
)
// ReportRepository 报告记录仓储接口
type ReportRepository interface {
// Create 创建报告记录
Create(ctx context.Context, report *entities.Report) error
// FindByReportID 根据报告编号查询记录
FindByReportID(ctx context.Context, reportID string) (*entities.Report, error)
}

View File

@@ -6,20 +6,28 @@ import (
"fmt" "fmt"
"tyapi-server/internal/application/api/commands" "tyapi-server/internal/application/api/commands"
appconfig "tyapi-server/internal/config"
api_repositories "tyapi-server/internal/domains/api/repositories"
"tyapi-server/internal/domains/api/services/processors" "tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/domains/api/services/processors/comb" "tyapi-server/internal/domains/api/services/processors/comb"
"tyapi-server/internal/domains/api/services/processors/dwbg" "tyapi-server/internal/domains/api/services/processors/dwbg"
"tyapi-server/internal/domains/api/services/processors/flxg" "tyapi-server/internal/domains/api/services/processors/flxg"
"tyapi-server/internal/domains/api/services/processors/ivyz" "tyapi-server/internal/domains/api/services/processors/ivyz"
"tyapi-server/internal/domains/api/services/processors/jrzq" "tyapi-server/internal/domains/api/services/processors/jrzq"
"tyapi-server/internal/domains/api/services/processors/pdfg"
"tyapi-server/internal/domains/api/services/processors/qcxg" "tyapi-server/internal/domains/api/services/processors/qcxg"
"tyapi-server/internal/domains/api/services/processors/qygl" "tyapi-server/internal/domains/api/services/processors/qygl"
"tyapi-server/internal/domains/api/services/processors/test" "tyapi-server/internal/domains/api/services/processors/test"
"tyapi-server/internal/domains/api/services/processors/yysy" "tyapi-server/internal/domains/api/services/processors/yysy"
"tyapi-server/internal/domains/product/services" "tyapi-server/internal/domains/product/services"
"tyapi-server/internal/infrastructure/external/alicloud" "tyapi-server/internal/infrastructure/external/alicloud"
"tyapi-server/internal/infrastructure/external/haiyuapi"
"tyapi-server/internal/infrastructure/external/huibo"
"tyapi-server/internal/infrastructure/external/jiguang" "tyapi-server/internal/infrastructure/external/jiguang"
"tyapi-server/internal/infrastructure/external/muzi" "tyapi-server/internal/infrastructure/external/muzi"
"tyapi-server/internal/infrastructure/external/nuoer"
"tyapi-server/internal/infrastructure/external/shujubao"
"tyapi-server/internal/infrastructure/external/shumai"
"tyapi-server/internal/infrastructure/external/tianyancha" "tyapi-server/internal/infrastructure/external/tianyancha"
"tyapi-server/internal/infrastructure/external/westdex" "tyapi-server/internal/infrastructure/external/westdex"
"tyapi-server/internal/infrastructure/external/xingwei" "tyapi-server/internal/infrastructure/external/xingwei"
@@ -45,10 +53,14 @@ type ApiRequestService struct {
validator interfaces.RequestValidator validator interfaces.RequestValidator
processorDeps *processors.ProcessorDependencies processorDeps *processors.ProcessorDependencies
combService *comb.CombService combService *comb.CombService
config *appconfig.Config
reportRepo api_repositories.ReportRepository
} }
func NewApiRequestService( func NewApiRequestService(
westDexService *westdex.WestDexService, westDexService *westdex.WestDexService,
shujubaoService *shujubao.ShujubaoService,
muziService *muzi.MuziService, muziService *muzi.MuziService,
yushanService *yushan.YushanService, yushanService *yushan.YushanService,
tianYanChaService *tianyancha.TianYanChaService, tianYanChaService *tianyancha.TianYanChaService,
@@ -56,14 +68,86 @@ func NewApiRequestService(
zhichaService *zhicha.ZhichaService, zhichaService *zhicha.ZhichaService,
xingweiService *xingwei.XingweiService, xingweiService *xingwei.XingweiService,
jiguangService *jiguang.JiguangService, jiguangService *jiguang.JiguangService,
shumaiService *shumai.ShumaiService,
huiboService *huibo.HuiboService,
nuoerService *nuoer.NuoerService,
haiyuapiService *haiyuapi.HaiyuapiService,
validator interfaces.RequestValidator, validator interfaces.RequestValidator,
productManagementService *services.ProductManagementService, productManagementService *services.ProductManagementService,
cfg *appconfig.Config,
) *ApiRequestService {
return NewApiRequestServiceWithRepos(
westDexService,
shujubaoService,
muziService,
yushanService,
tianYanChaService,
alicloudService,
zhichaService,
xingweiService,
jiguangService,
shumaiService,
huiboService,
nuoerService,
haiyuapiService,
validator,
productManagementService,
cfg,
nil,
nil,
)
}
// NewApiRequestServiceWithRepos 带自定义仓储的构造函数,便于扩展(例如企业报告记录)
func NewApiRequestServiceWithRepos(
westDexService *westdex.WestDexService,
shujubaoService *shujubao.ShujubaoService,
muziService *muzi.MuziService,
yushanService *yushan.YushanService,
tianYanChaService *tianyancha.TianYanChaService,
alicloudService *alicloud.AlicloudService,
zhichaService *zhicha.ZhichaService,
xingweiService *xingwei.XingweiService,
jiguangService *jiguang.JiguangService,
shumaiService *shumai.ShumaiService,
huiboService *huibo.HuiboService,
nuoerService *nuoer.NuoerService,
haiyuapiService *haiyuapi.HaiyuapiService,
validator interfaces.RequestValidator,
productManagementService *services.ProductManagementService,
cfg *appconfig.Config,
reportRepo api_repositories.ReportRepository,
qyglReportPDFScheduler processors.QYGLReportPDFScheduler,
) *ApiRequestService { ) *ApiRequestService {
// 创建组合包服务 // 创建组合包服务
combService := comb.NewCombService(productManagementService) combService := comb.NewCombService(productManagementService)
apiPublicBase := ""
if cfg != nil {
apiPublicBase = appconfig.ResolveAPIPublicBaseURL(&cfg.API)
}
// 创建处理器依赖容器 // 创建处理器依赖容器
processorDeps := processors.NewProcessorDependencies(westDexService, muziService, yushanService, tianYanChaService, alicloudService, zhichaService, xingweiService, jiguangService, validator, combService) processorDeps := processors.NewProcessorDependencies(
westDexService,
shujubaoService,
muziService,
yushanService,
tianYanChaService,
alicloudService,
zhichaService,
xingweiService,
jiguangService,
shumaiService,
huiboService,
nuoerService,
haiyuapiService,
validator,
combService,
reportRepo,
qyglReportPDFScheduler,
apiPublicBase,
)
// 统一注册所有处理器 // 统一注册所有处理器
registerAllProcessors(combService) registerAllProcessors(combService)
@@ -77,6 +161,8 @@ func NewApiRequestService(
validator: validator, validator: validator,
processorDeps: processorDeps, processorDeps: processorDeps,
combService: combService, combService: combService,
config: cfg,
reportRepo: reportRepo,
} }
} }
@@ -109,11 +195,15 @@ func registerAllProcessors(combService *comb.CombService) {
"FLXG7E8F": flxg.ProcessFLXG7E8FRequest, "FLXG7E8F": flxg.ProcessFLXG7E8FRequest,
"FLXG3A9B": flxg.ProcessFLXG3A9BRequest, "FLXG3A9B": flxg.ProcessFLXG3A9BRequest,
"FLXGK5D2": flxg.ProcessFLXGK5D2Request, "FLXGK5D2": flxg.ProcessFLXGK5D2Request,
"FLXGDJG3": flxg.ProcessFLXGDJG3Request, //董监高司法综合信息核验
"FLXGHB4F": flxg.ProcessFLXGHB4FRequest, //个人涉诉案件查询海宇API
// JRZQ系列处理器 // JRZQ系列处理器
"JRZQ8203": jrzq.ProcessJRZQ8203Request, "JRZQ8203": jrzq.ProcessJRZQ8203Request,
"JRZQ0A03": jrzq.ProcessJRZQ0A03Request, "JRZQ0A03": jrzq.ProcessJRZQ0A03Request,
"JRZQ4AA8": jrzq.ProcessJRZQ4AA8Request, "JRZQ4AA8": jrzq.ProcessJRZQ4AA8Request,
"JRZQDCBE": jrzq.ProcessJRZQDCBERequest, "JRZQDCBE": jrzq.ProcessJRZQDCBERequest,
"JRZQACAB": jrzq.ProcessJRZQACABERequest, // 银行卡四要素
"JRZQ09J8": jrzq.ProcessJRZQ09J8Request, "JRZQ09J8": jrzq.ProcessJRZQ09J8Request,
"JRZQ1D09": jrzq.ProcessJRZQ1D09Request, "JRZQ1D09": jrzq.ProcessJRZQ1D09Request,
"JRZQ3C7B": jrzq.ProcessJRZQ3C7BRequest, "JRZQ3C7B": jrzq.ProcessJRZQ3C7BRequest,
@@ -135,9 +225,19 @@ func registerAllProcessors(combService *comb.CombService) {
"JRZQ1W4X": jrzq.ProcessJRZQ1W4XRequest, "JRZQ1W4X": jrzq.ProcessJRZQ1W4XRequest,
"JRZQ3P01": jrzq.ProcessJRZQ3P01Request, "JRZQ3P01": jrzq.ProcessJRZQ3P01Request,
"JRZQ3AG6": jrzq.ProcessJRZQ3AG6Request, "JRZQ3AG6": jrzq.ProcessJRZQ3AG6Request,
"JRZQO6L7": jrzq.ProcessJRZQO6L7Request, // 全国自然人经济特征评分模型v3 简版 "JRZQO6L7": jrzq.ProcessJRZQO6L7Request, // 全国自然人经济特征评分模型v3 简版
"JRZQO7L1": jrzq.ProcessJRZQO7L1Request, // 全国自然人经济特征评分模型v4 详版 "JRZQO7L1": jrzq.ProcessJRZQO7L1Request, // 全国自然人经济特征评分模型v4 详版
"JRZQS7G0": jrzq.ProcessJRZQS7G0Request, // 社保综合评分V1 "JRZQS7G0": jrzq.ProcessJRZQS7G0Request, // 社保综合评分V1
"JRZQ1P5G": jrzq.ProcessJRZQ1P5GRequest, // 全国自然人借贷压力指数查询2
"JRZQOCRE": jrzq.ProcessJRZQOCREERequest, // 银行卡OCR数卖
"JRZQOCRY": jrzq.ProcessJRZQOCRYERequest, // 银行卡OCR数据宝
"JRZQV87M": jrzq.ProcessJRZQV87MRequest, // 金融黑名单V110_c10
"JRZQV0MD": jrzq.ProcessJRZQV0MDRequest, // 行为黑名单
"JRZQVT43": jrzq.ProcessJRZQVT43Request, // 投诉黑名单
"JRZQV3HM": jrzq.ProcessJRZQV3HMRequest, // 债务逾期黑名单
"JRZQV4TF": jrzq.ProcessJRZQV4TFRequest, // 智瞳分尊享版
"JRZQV5F4": jrzq.ProcessJRZQV5F4Request, // 风险变量V5F4
"JRZQV7MD": jrzq.ProcessJRZQV7MDRequest, // 特殊名单
// QYGL系列处理器 // QYGL系列处理器
"QYGL8261": qygl.ProcessQYGL8261Request, "QYGL8261": qygl.ProcessQYGL8261Request,
@@ -166,26 +266,54 @@ func registerAllProcessors(combService *comb.CombService) {
"QYGL2NAO": qygl.ProcessQYGL2naoRequest, //股权变更 "QYGL2NAO": qygl.ProcessQYGL2naoRequest, //股权变更
"QYGLNIO8": qygl.ProcessQYGLNIO8Request, //企业基本信息 "QYGLNIO8": qygl.ProcessQYGLNIO8Request, //企业基本信息
"QYGLP0HT": qygl.ProcessQYGLP0HTRequest, //股权穿透 "QYGLP0HT": qygl.ProcessQYGLP0HTRequest, //股权穿透
"QYGL5S1I": qygl.ProcessQYGL5S1IRequest, //企业司法涉诉I
"QYGLJ1U9": qygl.ProcessQYGLJ1U9Request, //企业全景报告(聚合 QYGLUY3S/QYGLJ0Q1/QYGL5S1I
"QYGLJ0Q1": qygl.ProcessQYGLJ0Q1Request, //企业股权结构全景查询
"QYGLUY3S": qygl.ProcessQYGLUY3SRequest, //企业经营状态全景查询
"QYGLDJ12": qygl.ProcessQYGLDJ12Request, //企业年报信息核验
"QYGL8848": qygl.ProcessQYGL8848Request, //企业税收违法核查
"QYGLDJ33": qygl.ProcessQYGLDJ33Request, //企业年报信息核验
"QYGL4YAB": qygl.ProcessQYGL4YABRequest, //企业四要素认证shumai
"QYGL3YSB": qygl.ProcessQYGL3YSBRequest, //企业三要素认证shumai
"QYGL2YSB": qygl.ProcessQYGL2YSBRequest, //企业二要素认证shumai
"QYGLDG77": qygl.ProcessQYGLDG77Request, //企业对公打款认证shumai
"QYGLBH7Y": qygl.ProcessQYGLBH7YRequest, //企业涉诉案件查询海宇
// YYSY系列处理器 // YYSY系列处理器
"YYSYD50F": yysy.ProcessYYSYD50FRequest, "YYSY35TA": yysy.ProcessYYSY35TARequest, //运营商归属地数卖
"YYSY09CD": yysy.ProcessYYSY09CDRequest, "YYSYD50F": yysy.ProcessYYSYD50FRequest,
"YYSY4B21": yysy.ProcessYYSY4B21Request, "YYSY09CD": yysy.ProcessYYSY09CDRequest,
"YYSY4B37": yysy.ProcessYYSY4B37Request, "YYSY4B21": yysy.ProcessYYSY4B21Request,
"YYSY6F2E": yysy.ProcessYYSY6F2ERequest, "YYSY4B37": yysy.ProcessYYSY4B37Request,
"YYSYBE08": yysy.ProcessYYSYBE08Request, "YYSY6F2E": yysy.ProcessYYSY6F2ERequest,
"YYSYF7DB": yysy.ProcessYYSYF7DBRequest, "YYSYBE08": yysy.ProcessYYSYBE08Request,
"YYSY4F2E": yysy.ProcessYYSY4F2ERequest, "YYSYBE08TEST": yysy.ProcessYYSYBE08testRequest, // 二要素(阿里云市场),与 YYSYBE08 入参一致
"YYSY8B1C": yysy.ProcessYYSY8B1CRequest, "YYSYF7DB": yysy.ProcessYYSYF7DBRequest,
"YYSY6D9A": yysy.ProcessYYSY6D9ARequest, "YYSY4F2E": yysy.ProcessYYSY4F2ERequest,
"YYSY3E7F": yysy.ProcessYYSY3E7FRequest, "YYSY8B1C": yysy.ProcessYYSY8B1CRequest,
"YYSY8F3A": yysy.ProcessYYSY8F3ARequest, "YYSY6D9A": yysy.ProcessYYSY6D9ARequest,
"YYSY9A1B": yysy.ProcessYYSY9A1BRequest, "YYSY3E7F": yysy.ProcessYYSY3E7FRequest,
"YYSY8C2D": yysy.ProcessYYSY8C2DRequest, "YYSY8F3A": yysy.ProcessYYSY8F3ARequest,
"YYSY7D3E": yysy.ProcessYYSY7D3ERequest, "YYSY9A1B": yysy.ProcessYYSY9A1BRequest,
"YYSY9E4A": yysy.ProcessYYSY9E4ARequest, "YYSY8C2D": yysy.ProcessYYSY8C2DRequest,
"YYSY9F1B": yysy.ProcessYYSY9F1BYequest, "YYSY7D3E": yysy.ProcessYYSY7D3ERequest,
"YYSY6F2B": yysy.ProcessYYSY6F2BRequest, "YYSY9E4A": yysy.ProcessYYSY9E4ARequest,
"YYSY9F1B": yysy.ProcessYYSY9F1BYequest,
"YYSY6F2B": yysy.ProcessYYSY6F2BRequest,
"YYSY3M8S": yysy.ProcessYYSY3M8SRequest, //运营商二要素查询
"YYSYC4R9": yysy.ProcessYYSYC4R9Request, //运营商三要素详版查询
"YYSYH6D2": yysy.ProcessYYSYH6D2Request, //运营商三要素简版查询
"YYSYP0T4": yysy.ProcessYYSYP0T4Request, //在网时长查询
"YYSYE7V5": yysy.ProcessYYSYE7V5Request, //手机在网状态查询
"YYSYS9W1": yysy.ProcessYYSYS9W1Request, //手机携号转网查询
"YYSYK8R3": yysy.ProcessYYSYK8R3Request, //手机空号检测查询
"YYSYH6F3": yysy.ProcessYYSYH6F3Request, //运营商三要素即时版查询
"YYSYK9R4": yysy.ProcessYYSYK9R4Request, //全网手机三要素验证1979周更新版
"YYSYF2T7": yysy.ProcessYYSYF2T7Request, //手机二次放号检测查询
"YYSYTJC4": yysy.ProcessYYSYTJC4Request, //运营商近三个月欠费次数脉
"YYSYP72D": yysy.ProcessYYSYP72DRequest, //运营商近三个月平均账单
"YYSYT7C4": yysy.ProcessYYSYT7C4Request, //手机停机验证
"YYSYXF7J": yysy.ProcessYYSYXF7JRequest, //手机消费区间验证
// IVYZ系列处理器 // IVYZ系列处理器
"IVYZ0B03": ivyz.ProcessIVYZ0B03Request, "IVYZ0B03": ivyz.ProcessIVYZ0B03Request,
@@ -207,6 +335,7 @@ func registerAllProcessors(combService *comb.CombService) {
"IVYZ3A7F": ivyz.ProcessIVYZ3A7FRequest, "IVYZ3A7F": ivyz.ProcessIVYZ3A7FRequest,
"IVYZ9D2E": ivyz.ProcessIVYZ9D2ERequest, "IVYZ9D2E": ivyz.ProcessIVYZ9D2ERequest,
"IVYZ81NC": ivyz.ProcessIVYZ81NCRequest, "IVYZ81NC": ivyz.ProcessIVYZ81NCRequest,
"IVYZ2MN6": ivyz.ProcessIVYZ2MN6Request,
"IVYZ6G7H": ivyz.ProcessIVYZ6G7HRequest, "IVYZ6G7H": ivyz.ProcessIVYZ6G7HRequest,
"IVYZ8I9J": ivyz.ProcessIVYZ8I9JRequest, "IVYZ8I9J": ivyz.ProcessIVYZ8I9JRequest,
"IVYZ9K2L": ivyz.ProcessIVYZ9K2LRequest, "IVYZ9K2L": ivyz.ProcessIVYZ9K2LRequest,
@@ -217,10 +346,29 @@ func registerAllProcessors(combService *comb.CombService) {
"IVYZ6M8P": ivyz.ProcessIVYZ6M8PRequest, //职业资格证书 "IVYZ6M8P": ivyz.ProcessIVYZ6M8PRequest, //职业资格证书
"IVYZ9H2M": ivyz.ProcessIVYZ9H2MRequest, //极光个人婚姻查询V2版 "IVYZ9H2M": ivyz.ProcessIVYZ9H2MRequest, //极光个人婚姻查询V2版
"IVYZZQT3": ivyz.ProcessIVYZZQT3Request, //人脸比对V3 "IVYZZQT3": ivyz.ProcessIVYZZQT3Request, //人脸比对V3
"IVYZZQ3B": ivyz.ProcessIVYZZQ3BRequest, //人脸比对BBsimilarity + verification_result
"IVYZBPQ2": ivyz.ProcessIVYZBPQ2Request, //人脸比对V2 "IVYZBPQ2": ivyz.ProcessIVYZBPQ2Request, //人脸比对V2
"IVYZSFEL": ivyz.ProcessIVYZSFELRequest, //全国自然人人像三要素核验_V1 "IVYZSFEL": ivyz.ProcessIVYZSFELRequest, //全国自然人人像三要素核验_V1
"IVYZ0S0D": ivyz.ProcessIVYZ0S0DRequest, //劳动仲裁信息查询(个人版) "IVYZ0S0D": ivyz.ProcessIVYZ0S0DRequest, //劳动仲裁信息查询(个人版)
"IVYZ1J7H": ivyz.ProcessIVYZ1J7HRequest, //行驶证核查v2 "IVYZ1J7H": ivyz.ProcessIVYZ1J7HRequest, //行驶证核查v2
"IVYZ9K7F": ivyz.ProcessIVYZ9K7FRequest, //身份证实名认证即时版
"IVYZA1B3": ivyz.ProcessIVYZA1B3Request, //公安三要素人脸识别
"IVYZFIC1": ivyz.ProcessIVYZFIC1Request, //人脸身份证比对(数脉)
"IVYZN2P8": ivyz.ProcessIVYZN2P8Request, //身份证实名认证政务版
"IVYZX5QZ": ivyz.ProcessIVYZX5QZRequest, //活体检测
"IVYZX5Q2": ivyz.ProcessIVYZX5Q2Request, //活体识别步骤二
"IVYZOCR1": ivyz.ProcessIVYZOCR1Request, //身份证OCR
"IVYZOCR2": ivyz.ProcessIVYZOCR2Request, //身份证OCR2数卖
"IVYZ18HY": ivyz.ProcessIVYZ18HYRequest, //婚姻状况核验V2单人
"IVYZ28HY": ivyz.ProcessIVYZ28HYRequest, //婚姻状况核验(单人)
"IVYZ38SR": ivyz.ProcessIVYZ38SRRequest, //婚姻状态核验(双人)
"IVYZ48SR": ivyz.ProcessIVYZ48SRRequest, //婚姻状态核验V2双人
"IVYZ5E22": ivyz.ProcessIVYZ5E22Request, //双人婚姻评估查询zhicha版本
"IVYZRAX1": ivyz.ProcessIVYZRAX1Request, //融安信用分
"IVYZRAX2": ivyz.ProcessIVYZRAX2Request, //融御反欺诈分
"IVYZ2MN7": ivyz.ProcessIVYZ2MN7Request, //学历B
"IVYZ4Y27": ivyz.ProcessIVYZ4Y27Request, //学历高级版
"IVYZVJJ6": ivyz.ProcessIVYZVJJ6Request, //收入等级tax_income_level_v8
// COMB系列处理器 - 只注册有自定义逻辑的组合包 // COMB系列处理器 - 只注册有自定义逻辑的组合包
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑重命名ApiCode "COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑重命名ApiCode
@@ -233,24 +381,39 @@ func registerAllProcessors(combService *comb.CombService) {
"QCXG8A3D": qcxg.ProcessQCXG8A3DRequest, "QCXG8A3D": qcxg.ProcessQCXG8A3DRequest,
"QCXG6B4E": qcxg.ProcessQCXG6B4ERequest, "QCXG6B4E": qcxg.ProcessQCXG6B4ERequest,
"QCXG4896": qcxg.ProcessQCXG4896Request, "QCXG4896": qcxg.ProcessQCXG4896Request,
"QCXG5F3A": qcxg.ProcessQCXG5F3ARequest, // 极光个人车辆查询 "QCXG5F3A": qcxg.ProcessQCXG5F3ARequest, // 极光个人车辆查询
"QCXG4D2E": qcxg.ProcessQCXG4D2ERequest, // 极光名下车辆数量查询 "QCXG4D2E": qcxg.ProcessQCXG4D2ERequest, // 极光名下车辆数量查询
"QCXGJJ2A": qcxg.ProcessQCXGJJ2ARequest, // vin码查车辆信息(一对多) "QCXGJJ2A": qcxg.ProcessQCXGJJ2ARequest, // vin码查车辆信息(一对多)
"QCXGGJ3A": qcxg.ProcessQCXGGJ3ARequest, // 车辆vin码查询号牌 "QCXGGJ3A": qcxg.ProcessQCXGGJ3ARequest, // 车辆vin码查询号牌
"QCXGYTS2": qcxg.ProcessQCXGYTS2Request, // 车辆二要素核验v2 "QCXGYTS2": qcxg.ProcessQCXGYTS2Request, // 车辆二要素核验v2
"QCXGP00W": qcxg.ProcessQCXGP00WRequest, // 车辆出险详版查询 "QCXGP00W": qcxg.ProcessQCXGP00WRequest, // 车辆出险详版查询
"QCXGGB2Q": qcxg.ProcessQCXGGB2QRequest, // 车辆二要素核验V1 "QCXGGB2Q": qcxg.ProcessQCXGGB2QRequest, // 车辆二要素核验V1
"QCXG4I1Z": qcxg.ProcessQCXG4I1ZRequest, // 车辆过户详版查询 "QCXG4I1Z": qcxg.ProcessQCXG4I1ZRequest, // 车辆过户详版查询
"QCXG1H7Y": qcxg.ProcessQCXG1H7YRequest, // 车辆过户简版查询 "QCXG1H7Y": qcxg.ProcessQCXG1H7YRequest, // 车辆过户简版查询
"QCXG3Z3L": qcxg.ProcessQCXG3Z3LRequest, // 车辆维保详细版查询 "QCXG3Z3L": qcxg.ProcessQCXG3Z3LRequest, // 车辆维保详细版查询
"QCXG3Y6B": qcxg.ProcessQCXG3Y6BRequest, // 车辆维保简版查询 "QCXG3Y6B": qcxg.ProcessQCXG3Y6BRequest, // 车辆维保简版查询
"QCXG2T6S": qcxg.ProcessQCXG2T6SRequest, // 车辆里程记录(品牌查询) "QCXG2T6S": qcxg.ProcessQCXG2T6SRequest, // 车辆里程记录(品牌查询)
"QCXG1U4U": qcxg.ProcessQCXG1U4URequest, // 车辆里程记录(混合查询) "QCXG1U4U": qcxg.ProcessQCXG1U4URequest, //
"QCXG9F5C": qcxg.ProcessQCXG9F5CERequest, //疑似营运车辆注册平台数 10386
"QCXG3B8Z": qcxg.ProcessQCXG3B8ZRequest, //疑似运营车辆查询月度里程10268
"QCXGP1W3": qcxg.ProcessQCXGP1W3Request, //疑似运营车辆查询季度里程10269
"QCXGM7R9": qcxg.ProcessQCXGM7R9Request, //疑似运营车辆查询半年度里程10270
"QCXGU2K4": qcxg.ProcessQCXGU2K4Request, //疑似运营车辆查询年度里程10271
"QCXG5U0Z": qcxg.ProcessQCXG5U0ZRequest, // 车辆静态信息查询 10479
"QCXGY7F2": qcxg.ProcessQCXGY7F2Request, // 二手车VIN估值 10443
"QCXG3M7Z": qcxg.ProcessQCXG3M7ZRequest, //人车关系核验ETC10093 月更
"QCXGM4CL": qcxg.ProcessQCXGM4CLRequest, //名下车辆诺尔
"QYGLVR76": qygl.ProcessQYGLVR76Request, //名下企业诺尔
"QCXG1S2L": qcxg.ProcessQCXG1S2LRequest, //车辆车五项信息核验V2
"QCXGA8V3": qcxg.ProcessQCXGA8V3Request, //全国车辆配置查验(车五项+使用性质+承保)
// DWBG系列处理器 - 多维报告 // DWBG系列处理器 - 多维报告
"DWBG6A2C": dwbg.ProcessDWBG6A2CRequest, "DWBG6A2C": dwbg.ProcessDWBG6A2CRequest,
"DWBG8B4D": dwbg.ProcessDWBG8B4DRequest, "DWBG8B4D": dwbg.ProcessDWBG8B4DRequest,
"DWBG7F3A": dwbg.ProcessDWBG7F3ARequest, "DWBG7F3A": dwbg.ProcessDWBG7F3ARequest,
"DWBG5SAM": dwbg.ProcessDWBG5SAMRequest,
"DWBG3BF9": dwbg.ProcessDWBG3BF9Request, // 个人风险档案max
"DWBG9FB3": dwbg.ProcessDWBG9FB3Request, // 个人风险档案1
"DWBG9FB2": dwbg.ProcessDWBG9FB2Request, // 个人风险档案2
// FLXG系列处理器 - 风险管控 (包含原FXHY功能) // FLXG系列处理器 - 风险管控 (包含原FXHY功能)
"FLXG8B4D": flxg.ProcessFLXG8B4DRequest, "FLXG8B4D": flxg.ProcessFLXG8B4DRequest,
@@ -259,6 +422,9 @@ func registerAllProcessors(combService *comb.CombService) {
"TEST001": test.ProcessTestRequest, "TEST001": test.ProcessTestRequest,
"TEST002": test.ProcessTestErrorRequest, "TEST002": test.ProcessTestErrorRequest,
"TEST003": test.ProcessTestTimeoutRequest, "TEST003": test.ProcessTestTimeoutRequest,
// PDFG系列处理器 - PDF生成
"PDFG01GZ": pdfg.ProcessPDFG01GZRequest,
} }
// 批量注册到组合包服务 // 批量注册到组合包服务
@@ -280,6 +446,8 @@ func (a *ApiRequestService) PreprocessRequestApi(ctx context.Context, apiCode st
// 将apiCode放入context供外部服务使用 // 将apiCode放入context供外部服务使用
ctx = context.WithValue(ctx, "api_code", apiCode) ctx = context.WithValue(ctx, "api_code", apiCode)
// 将config放入context供处理器使用
ctx = context.WithValue(ctx, "config", a.config)
// 1. 优先查找已注册的自定义处理器 // 1. 优先查找已注册的自定义处理器
if processor, exists := RequestProcessors[apiCode]; exists { if processor, exists := RequestProcessors[apiCode]; exists {

View File

@@ -76,158 +76,237 @@ func (s *FormConfigServiceImpl) GetFormConfig(ctx context.Context, apiCode strin
func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string) (interface{}, error) { func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string) (interface{}, error) {
// 建立API代码到DTO结构体的映射 // 建立API代码到DTO结构体的映射
dtoMap := map[string]interface{}{ dtoMap := map[string]interface{}{
"IVYZ9363": &dto.IVYZ9363Req{}, "IVYZ9363": &dto.IVYZ9363Req{},
"IVYZ385E": &dto.IVYZ385EReq{}, "IVYZ385E": &dto.IVYZ385EReq{},
"IVYZ5733": &dto.IVYZ5733Req{}, "IVYZ5733": &dto.IVYZ5733Req{},
"FLXG3D56": &dto.FLXG3D56Req{}, "FLXG3D56": &dto.FLXG3D56Req{},
"FLXG75FE": &dto.FLXG75FEReq{}, "FLXG75FE": &dto.FLXG75FEReq{},
"FLXG0V3B": &dto.FLXG0V3BReq{}, "FLXG0V3B": &dto.FLXG0V3BReq{},
"FLXG0V4B": &dto.FLXG0V4BReq{}, "FLXG0V4B": &dto.FLXG0V4BReq{},
"FLXG54F5": &dto.FLXG54F5Req{}, "FLXG54F5": &dto.FLXG54F5Req{},
"FLXG162A": &dto.FLXG162AReq{}, "FLXG162A": &dto.FLXG162AReq{},
"FLXG0687": &dto.FLXG0687Req{}, "FLXG0687": &dto.FLXG0687Req{},
"FLXGBC21": &dto.FLXGBC21Req{}, "FLXGBC21": &dto.FLXGBC21Req{},
"FLXG970F": &dto.FLXG970FReq{}, "FLXG970F": &dto.FLXG970FReq{},
"FLXG5876": &dto.FLXG5876Req{}, "FLXG5876": &dto.FLXG5876Req{},
"FLXG9687": &dto.FLXG9687Req{}, "FLXG9687": &dto.FLXG9687Req{},
"FLXGC9D1": &dto.FLXGC9D1Req{}, "FLXGC9D1": &dto.FLXGC9D1Req{},
"FLXGCA3D": &dto.FLXGCA3DReq{}, "FLXGCA3D": &dto.FLXGCA3DReq{},
"FLXGDEC7": &dto.FLXGDEC7Req{}, "FLXGDEC7": &dto.FLXGDEC7Req{},
"JRZQ0A03": &dto.JRZQ0A03Req{}, "JRZQ0A03": &dto.JRZQ0A03Req{},
"JRZQ4AA8": &dto.JRZQ4AA8Req{}, "JRZQ4AA8": &dto.JRZQ4AA8Req{},
"JRZQ8203": &dto.JRZQ8203Req{}, "JRZQ8203": &dto.JRZQ8203Req{},
"JRZQDCBE": &dto.JRZQDCBEReq{}, "JRZQDCBE": &dto.JRZQDCBEReq{},
"QYGL2ACD": &dto.QYGL2ACDReq{}, "QYGL2ACD": &dto.QYGL2ACDReq{},
"QYGL6F2D": &dto.QYGL6F2DReq{}, "QYGL6F2D": &dto.QYGL6F2DReq{},
"QYGL45BD": &dto.QYGL45BDReq{}, "QYGL45BD": &dto.QYGL45BDReq{},
"QYGL8261": &dto.QYGL8261Req{}, "QYGL8261": &dto.QYGL8261Req{},
"QYGL8271": &dto.QYGL8271Req{}, "QYGL8271": &dto.QYGL8271Req{},
"QYGLB4C0": &dto.QYGLB4C0Req{}, "QYGLB4C0": &dto.QYGLB4C0Req{},
"QYGL23T7": &dto.QYGL23T7Req{}, "QYGL23T7": &dto.QYGL23T7Req{},
"QYGL5A3C": &dto.QYGL5A3CReq{}, "QYGL5A3C": &dto.QYGL5A3CReq{},
"QYGL8B4D": &dto.QYGL8B4DReq{}, "QYGL8B4D": &dto.QYGL8B4DReq{},
"QYGL9E2F": &dto.QYGL9E2FReq{}, "QYGL9E2F": &dto.QYGL9E2FReq{},
"QYGL7C1A": &dto.QYGL7C1AReq{}, "QYGL7C1A": &dto.QYGL7C1AReq{},
"QYGL3F8E": &dto.QYGL3F8EReq{}, "QYGL3F8E": &dto.QYGL3F8EReq{},
"YYSY4B37": &dto.YYSY4B37Req{}, "YYSY4B37": &dto.YYSY4B37Req{},
"YYSY4B21": &dto.YYSY4B21Req{}, "YYSY4B21": &dto.YYSY4B21Req{},
"YYSY6F2E": &dto.YYSY6F2EReq{}, "YYSY6F2E": &dto.YYSY6F2EReq{},
"YYSY09CD": &dto.YYSY09CDReq{}, "YYSY09CD": &dto.YYSY09CDReq{},
"IVYZ0B03": &dto.IVYZ0B03Req{}, "IVYZ0B03": &dto.IVYZ0B03Req{},
"YYSYBE08": &dto.YYSYBE08Req{}, "YYSYBE08": &dto.YYSYBE08Req{},
"YYSYD50F": &dto.YYSYD50FReq{}, "YYSYBE08TEST": &dto.YYSYBE08Req{},
"YYSYF7DB": &dto.YYSYF7DBReq{}, "YYSYD50F": &dto.YYSYD50FReq{},
"IVYZ9A2B": &dto.IVYZ9A2BReq{}, "YYSYF7DB": &dto.YYSYF7DBReq{},
"IVYZ7F2A": &dto.IVYZ7F2AReq{}, "IVYZ9A2B": &dto.IVYZ9A2BReq{},
"IVYZ4E8B": &dto.IVYZ4E8BReq{}, "IVYZ7F2A": &dto.IVYZ7F2AReq{},
"IVYZ1C9D": &dto.IVYZ1C9DReq{}, "IVYZ4E8B": &dto.IVYZ4E8BReq{},
"IVYZGZ08": &dto.IVYZGZ08Req{}, "IVYZ4Y27": &dto.IVYZ4Y27Req{}, //教育背景详细PDF授权书
"FLXG8A3F": &dto.FLXG8A3FReq{}, "IVYZ1C9D": &dto.IVYZ1C9DReq{},
"FLXG5B2E": &dto.FLXG5B2EReq{}, "IVYZGZ08": &dto.IVYZGZ08Req{},
"COMB298Y": &dto.COMB298YReq{}, "FLXG8A3F": &dto.FLXG8A3FReq{},
"COMB86PM": &dto.COMB86PMReq{}, "FLXG5B2E": &dto.FLXG5B2EReq{},
"QCXG7A2B": &dto.QCXG7A2BReq{}, "COMB298Y": &dto.COMB298YReq{},
"COMENT01": &dto.COMENT01Req{}, "COMB86PM": &dto.COMB86PMReq{},
"JRZQ09J8": &dto.JRZQ09J8Req{}, "QCXG7A2B": &dto.QCXG7A2BReq{},
"FLXGDEA8": &dto.FLXGDEA8Req{}, "COMENT01": &dto.COMENT01Req{},
"FLXGDEA9": &dto.FLXGDEA9Req{}, "JRZQ09J8": &dto.JRZQ09J8Req{},
"JRZQ1D09": &dto.JRZQ1D09Req{}, "FLXGDEA8": &dto.FLXGDEA8Req{},
"IVYZ2A8B": &dto.IVYZ2A8BReq{}, "FLXGDEA9": &dto.FLXGDEA9Req{},
"IVYZ7C9D": &dto.IVYZ7C9DReq{}, "JRZQ1D09": &dto.JRZQ1D09Req{},
"IVYZ5E3F": &dto.IVYZ5E3FReq{}, "IVYZ2A8B": &dto.IVYZ2A8BReq{},
"YYSY4F2E": &dto.YYSY4F2EReq{}, "IVYZ7C9D": &dto.IVYZ7C9DReq{},
"YYSY8B1C": &dto.YYSY8B1CReq{}, "IVYZ5E3F": &dto.IVYZ5E3FReq{},
"YYSY6D9A": &dto.YYSY6D9AReq{}, "YYSY4F2E": &dto.YYSY4F2EReq{},
"YYSY3E7F": &dto.YYSY3E7FReq{}, "YYSY8B1C": &dto.YYSY8B1CReq{},
"FLXG5A3B": &dto.FLXG5A3BReq{}, "YYSY6D9A": &dto.YYSY6D9AReq{},
"FLXG9C1D": &dto.FLXG9C1DReq{}, "YYSY3E7F": &dto.YYSY3E7FReq{},
"FLXG2E8F": &dto.FLXG2E8FReq{}, "FLXG5A3B": &dto.FLXG5A3BReq{},
"JRZQ3C7B": &dto.JRZQ3C7BReq{}, "FLXG9C1D": &dto.FLXG9C1DReq{},
"JRZQ8A2D": &dto.JRZQ8A2DReq{}, "FLXG2E8F": &dto.FLXG2E8FReq{},
"JRZQ5E9F": &dto.JRZQ5E9FReq{}, "JRZQ3C7B": &dto.JRZQ3C7BReq{},
"JRZQ4B6C": &dto.JRZQ4B6CReq{}, "JRZQ8A2D": &dto.JRZQ8A2DReq{},
"JRZQ7F1A": &dto.JRZQ7F1AReq{}, "JRZQ5E9F": &dto.JRZQ5E9FReq{},
"DWBG6A2C": &dto.DWBG6A2CReq{}, "JRZQ4B6C": &dto.JRZQ4B6CReq{},
"DWBG8B4D": &dto.DWBG8B4DReq{}, "JRZQ7F1A": &dto.JRZQ7F1AReq{},
"FLXG8B4D": &dto.FLXG8B4DReq{}, "DWBG6A2C": &dto.DWBG6A2CReq{},
"IVYZ81NC": &dto.IVYZ81NCReq{}, "DWBG8B4D": &dto.DWBG8B4DReq{},
"IVYZ7F3A": &dto.IVYZ7F3AReq{}, "FLXG8B4D": &dto.FLXG8B4DReq{},
"IVYZ3P9M": &dto.IVYZ3P9MReq{}, "IVYZ81NC": &dto.IVYZ81NCReq{},
"IVYZ3A7F": &dto.IVYZ3A7FReq{}, "IVYZ2MN6": &dto.IVYZ2MN6Req{},
"IVYZ9D2E": &dto.IVYZ9D2EReq{}, "IVYZ7F3A": &dto.IVYZ7F3AReq{},
"IVYZ9K2L": &dto.IVYZ9K2LReq{}, "IVYZ3P9M": &dto.IVYZ3P9MReq{},
"DWBG7F3A": &dto.DWBG7F3AReq{}, "IVYZ3A7F": &dto.IVYZ3A7FReq{},
"YYSY8F3A": &dto.YYSY8F3AReq{}, "IVYZ9D2E": &dto.IVYZ9D2EReq{},
"QCXG9P1C": &dto.QCXG9P1CReq{}, "IVYZ9K2L": &dto.IVYZ9K2LReq{},
"JRZQ9E2A": &dto.JRZQ9E2AReq{}, "DWBG7F3A": &dto.DWBG7F3AReq{},
"YYSY9A1B": &dto.YYSY9A1BReq{}, "YYSY8F3A": &dto.YYSY8F3AReq{},
"YYSY8C2D": &dto.YYSY8C2DReq{}, "QCXG9P1C": &dto.QCXG9P1CReq{},
"YYSY7D3E": &dto.YYSY7D3EReq{}, "JRZQ9E2A": &dto.JRZQ9E2AReq{},
"YYSY9E4A": &dto.YYSY9E4AReq{}, "YYSY9A1B": &dto.YYSY9A1BReq{},
"JRZQ6F2A": &dto.JRZQ6F2AReq{}, "YYSY8C2D": &dto.YYSY8C2DReq{},
"JRZQ8B3C": &dto.JRZQ8B3CReq{}, "YYSY7D3E": &dto.YYSY7D3EReq{},
"JRZQ9D4E": &dto.JRZQ9D4EReq{}, "YYSY9E4A": &dto.YYSY9E4AReq{},
"FLXG7E8F": &dto.FLXG7E8FReq{}, "JRZQ6F2A": &dto.JRZQ6F2AReq{},
"QYGL5F6A": &dto.QYGL5F6AReq{}, "JRZQ8B3C": &dto.JRZQ8B3CReq{},
"IVYZ6G7H": &dto.IVYZ6G7HReq{}, "JRZQ9D4E": &dto.JRZQ9D4EReq{},
"IVYZ8I9J": &dto.IVYZ8I9JReq{}, "FLXG7E8F": &dto.FLXG7E8FReq{},
"JRZQ0L85": &dto.JRZQ0L85Req{}, "QYGL5F6A": &dto.QYGL5F6AReq{},
"COMBHZY2": &dto.COMBHZY2Req{}, // 自此无imp11.28 "IVYZ6G7H": &dto.IVYZ6G7HReq{},
"QCXG8A3D": &dto.QCXG8A3DReq{}, "IVYZ8I9J": &dto.IVYZ8I9JReq{},
"QCXG6B4E": &dto.QCXG6B4EReq{}, "JRZQ0L85": &dto.JRZQ0L85Req{},
"QYGL2B5C": &dto.QYGL2B5CReq{}, "COMBHZY2": &dto.COMBHZY2Req{}, //
"JRZQ2F8A": &dto.JRZQ2F8AReq{}, "QCXG8A3D": &dto.QCXG8A3DReq{},
"JRZQ1E7B": &dto.JRZQ1E7BReq{}, "QCXG6B4E": &dto.QCXG6B4EReq{},
"JRZQ3C9R": &dto.JRZQ3C9RReq{}, "QYGL2B5C": &dto.QYGL2B5CReq{},
"IVYZ2C1P": &dto.IVYZ2C1PReq{}, "QYGLJ1U9": &dto.QYGLJ1U9Req{},
"YYSY9F1B": &dto.YYSY9F1BReq{}, "JRZQ2F8A": &dto.JRZQ2F8AReq{},
"YYSY6F2B": &dto.YYSY6F2BReq{}, "JRZQ1E7B": &dto.JRZQ1E7BReq{},
"QYGL6S1B": &dto.QYGL6S1BReq{}, "JRZQ3C9R": &dto.JRZQ3C9RReq{},
"JRZQ0B6Y": &dto.JRZQ0B6YReq{}, "IVYZ2C1P": &dto.IVYZ2C1PReq{},
"JRZQ9A1W": &dto.JRZQ9A1WReq{}, "YYSY9F1B": &dto.YYSY9F1BReq{},
"JRZQ8F7C": &dto.JRZQ8F7CReq{}, //综合多头 "YYSY6F2B": &dto.YYSY6F2BReq{},
"FLXGK5D2": &dto.FLXGK5D2Req{}, "QYGL6S1B": &dto.QYGL6S1BReq{},
"FLXG3A9B": &dto.FLXG3A9BReq{}, "JRZQ0B6Y": &dto.JRZQ0B6YReq{},
"IVYZP2Q6": &dto.IVYZP2Q6Req{}, "JRZQ9A1W": &dto.JRZQ9A1WReq{},
"JRZQ1W4X": &dto.JRZQ1W4XReq{}, //全景档案 "JRZQ8F7C": &dto.JRZQ8F7CReq{}, //综合多头
"QYGL2S0W": &dto.QYGL2S0WReq{}, //失信被执行企业个人查询 "FLXGK5D2": &dto.FLXGK5D2Req{},
"QYGL9T1Q": &dto.QYGL9T1QReq{}, //全国企业借贷意向验证查询_V1 "FLXG3A9B": &dto.FLXG3A9BReq{},
"QYGL5A9T": &dto.QYGL5A9TReq{}, //全国企业各类工商风险统计数量查询 "IVYZP2Q6": &dto.IVYZP2Q6Req{},
"JRZQ3P01": &dto.JRZQ3P01Req{}, //天远风控决策 "JRZQ1W4X": &dto.JRZQ1W4XReq{}, //全景档案
"JRZQ3AG6": &dto.JRZQ3AG6Req{}, //轻松查公积 "QYGL2S0W": &dto.QYGL2S0WReq{}, //失信被执行企业个人查询
"IVYZ2B2T": &dto.IVYZ2B2TReq{}, //能力资质核验(学历) "QYGL9T1Q": &dto.QYGL9T1QReq{}, //全国企业借贷意向验证查询_V1
"IVYZ5A9O": &dto.IVYZ5A9OReq{}, //全国⾃然⼈⻛险评估评分模型 "QYGL5A9T": &dto.QYGL5A9TReq{}, //全国企业各类工商风险统计数量查询
"IVYZ6M8P": &dto.IVYZ6M8PReq{}, //职业资格证书 "JRZQ3P01": &dto.JRZQ3P01Req{}, //天远风控决策
"IVYZ9H2M": &dto.IVYZ9H2MReq{}, //极光个人婚姻查询V2版 "JRZQ3AG6": &dto.JRZQ3AG6Req{}, //轻松查公积
"QYGL5CMP": &dto.QYGL5CMPReq{}, //企业五要素验证 "IVYZ2B2T": &dto.IVYZ2B2TReq{}, //能力资质核验(学历)
"QCXG4896": &dto.QCXG4896Req{}, //网约车风险查询 "IVYZ5A9O": &dto.IVYZ5A9OReq{}, //全国⾃然⼈⻛险评估评分模型
"IVYZZQT3": &dto.IVYZZQT3Req{}, //人脸比对V3 "IVYZ6M8P": &dto.IVYZ6M8PReq{}, //职业资格证书
"IVYZBPQ2": &dto.IVYZBPQ2Req{}, //人脸比对V2 "IVYZ9H2M": &dto.IVYZ9H2MReq{}, //极光个人婚姻查询V2版
"IVYZSFEL": &dto.IVYZSFELReq{}, //全国自然人人像三要素核验_V1 "QYGL5CMP": &dto.QYGL5CMPReq{}, //企业五要素验证
"QYGL66SL": &dto.QYGL66SLReq{}, //全国企业司法模型服务查询_V1 "QCXG4896": &dto.QCXG4896Req{}, //网约车风险查询
"QCXG5F3A": &dto.QCXG5F3AReq{}, //极光个人车辆查询 "IVYZZQT3": &dto.IVYZZQT3Req{}, //人脸比对V3
"QCXG4D2E": &dto.QCXG4D2EReq{}, //极光名下车辆数量查询 "IVYZZQ3B": &dto.IVYZZQ3BReq{}, //人脸比对BBsimilarity + verification_result
"QYGLP0HT": &dto.QYGLP0HTReq{}, //股权穿透 "IVYZBPQ2": &dto.IVYZBPQ2Req{}, //人脸比对V2
"QYGL2NAO": &dto.QYGL2naoReq{}, //股权变更 "IVYZSFEL": &dto.IVYZSFELReq{}, //全国自然人人像三要素核验_V1
"QYGLNIO8": &dto.QYGLNIO8Req{}, //企业基本信息 "QYGL66SL": &dto.QYGL66SLReq{}, //全国企业司法模型服务查询_V1
"QYGL4B2E": &dto.QYGL5A3CReq{}, //税收违法 "QCXG5F3A": &dto.QCXG5F3AReq{}, //极光个人车辆查询
"QYGL7D9A": &dto.QYGL5A3CReq{}, //欠税公告 "QCXG4D2E": &dto.QCXG4D2EReq{}, //极光名下车辆数量查询
"IVYZ0S0D": &dto.IVYZ0S0DReq{}, //劳动仲裁信息查询(个人版) "QYGLP0HT": &dto.QYGLP0HTReq{}, //股权穿透
"IVYZ1J7H": &dto.IVYZ1J7HReq{}, //行驶证核查v2 "QYGL2NAO": &dto.QYGL2naoReq{}, //股权变更
"QCXGJJ2A": &dto.QCXGJJ2AReq{}, //vin码查车辆信息(一对多) "QYGLNIO8": &dto.QYGLNIO8Req{}, //企业基本信息
"QCXGGJ3A": &dto.QCXGGJ3AReq{}, //车辆vin码查询号牌 "QYGL4B2E": &dto.QYGL5A3CReq{}, //税收违法
"QCXGYTS2": &dto.QCXGYTS2Req{}, //车辆二要素核验v2 "QYGL7D9A": &dto.QYGL5A3CReq{}, //欠税公告
"QCXGP00W": &dto.QCXGP00WReq{}, //车辆出险详版查询 "IVYZ0S0D": &dto.IVYZ0S0DReq{}, //劳动仲裁信息查询(个人版)
"QCXGGB2Q": &dto.QCXGGB2QReq{}, //车辆二要素核验V1 "IVYZ1J7H": &dto.IVYZ1J7HReq{}, //行驶证核查v2
"QCXG4I1Z": &dto.QCXG4I1ZReq{}, //车辆过户详版查询 "QCXGJJ2A": &dto.QCXGJJ2AReq{}, //vin码查车辆信息(一对多)
"QCXG1H7Y": &dto.QCXG1H7YReq{}, //车辆过户简版查询 "QCXGGJ3A": &dto.QCXGGJ3AReq{}, //车辆vin码查询号牌
"QCXG3Z3L": &dto.QCXG3Z3LReq{}, //车辆维保详细版查询 "QCXGYTS2": &dto.QCXGYTS2Req{}, //车辆二要素核验v2
"QCXG3Y6B": &dto.QCXG1U4UReq{}, //车辆维保简版查询 "QCXGP00W": &dto.QCXGP00WReq{}, //车辆出险详版查询
"QCXG2T6S": &dto.QCXG2T6SReq{}, //车辆里程记录(品牌查询) "QCXGGB2Q": &dto.QCXGGB2QReq{}, //车辆二要素核验V1
"QCXG1U4U": &dto.QCXG1U4UReq{}, //车辆里程记录(混合查询 "QCXG4I1Z": &dto.QCXG4I1ZReq{}, //车辆过户详版查询
"JRZQO6L7": &dto.JRZQO6L7Req{}, //全国自然人经济特征评分模型v3 简版 "QCXG1H7Y": &dto.QCXG1H7YReq{}, //车辆过户简版查询
"JRZQO7L1": &dto.JRZQO7L1Req{}, //全国自然人经济特征评分模型v4 详版 "QCXG3Z3L": &dto.QCXG3Z3LReq{}, //车辆维保详细版查询
"JRZQS7G0": &dto.JRZQS7G0Req{}, //社保综合评分V1 "QCXG3Y6B": &dto.QCXG1U4UReq{}, //车辆维保简版查询
"QCXG2T6S": &dto.QCXG2T6SReq{}, //车辆里程记录(品牌查询)
"QCXG1U4U": &dto.QCXG1U4UReq{}, //车辆里程记录(混合查询)
"JRZQO6L7": &dto.JRZQO6L7Req{}, //全国自然人经济特征评分模型v3 简版
"JRZQO7L1": &dto.JRZQO7L1Req{}, //全国自然人经济特征评分模型v4 详版
"JRZQS7G0": &dto.JRZQS7G0Req{}, //社保综合评分V1
"IVYZ9K7F": &dto.IVYZ9K7FReq{}, //身份证实名认证即时版
"YYSY3M8S": &dto.YYSY3M8SReq{}, //运营商二要素查询
"YYSYC4R9": &dto.YYSYC4R9Req{}, //运营商三要素详版查询
"YYSYH6D2": &dto.YYSYH6D2Req{}, //运营商三要素简版政务版查询
"YYSYP0T4": &dto.YYSYP0T4Req{}, //在网时长查询
"YYSYE7V5": &dto.YYSYE7V5Req{}, //手机在网状态查询
"YYSYS9W1": &dto.YYSYS9W1Req{}, //手机携号转网查询
"YYSYK8R3": &dto.YYSYK8R3Req{}, //手机空号检测查询
"YYSYF2T7": &dto.YYSYF2T7Req{}, //手机二次放号检测查询
"IVYZA1B3": &dto.IVYZA1B3Req{}, //公安三要素人脸识别
"IVYZFIC1": &dto.IVYZFIC1Req{}, //人脸身份证比对(数脉)
"IVYZX5QZ": &dto.IVYZX5QZReq{}, //活体识别
"IVYZN2P8": &dto.IVYZ9K7FReq{}, //身份证实名认证政务版
"YYSYH6F3": &dto.YYSYH6F3Req{}, //运营商三要素简版即时版查询
"IVYZX5Q2": &dto.IVYZX5Q2Req{}, //活体识别步骤二
"PDFG01GZ": &dto.PDFG01GZReq{}, //
"QYGL5S1I": &dto.QYGL5S1IReq{}, //企业司法涉诉V2
"JRZQACAB": &dto.JRZQACABReq{}, //银行卡四要素
"QCXG9F5C": &dto.QCXG9F5CReq{}, //疑似营运车辆注册平台数 10386
"QCXG3B8Z": &dto.QCXG3B8ZReq{}, //疑似运营车辆查询月度里程10268
"QCXGP1W3": &dto.QCXGP1W3Req{}, //疑似运营车辆查询季度里程10269
"QCXGM7R9": &dto.QCXGM7R9Req{}, //疑似运营车辆查询半年度里程10270
"QCXGU2K4": &dto.QCXGU2K4Req{}, //疑似运营车辆查询年度里程10271
"QCXG5U0Z": &dto.QCXG5U0ZReq{}, //车辆静态信息查询 10479
"QCXGY7F2": &dto.QCXGY7F2Req{}, //二手车VIN估值 10443
"YYSYK9R4": &dto.YYSYK9R4Req{}, //全网手机三要素验证1979周更新版
"QCXG3M7Z": &dto.QCXG3M7ZReq{}, //人车关系核验ETC10093 月更
"JRZQ1P5G": &dto.JRZQ1P5GReq{}, //全国自然人借贷压力指数查询2
"IVYZOCR1": &dto.IVYZOCR1Req{}, //身份证OCR
"IVYZOCR2": &dto.IVYZOCR1Req{}, //身份证OCR2数卖
"QYGLJ0Q1": &dto.QYGLJ0Q1Req{}, //企业股权结构全景查询
"QYGLUY3S": &dto.QYGLUY3SReq{}, //企业全量信息核验V2 可用
"JRZQOCRE": &dto.JRZQOCREReq{}, //银行卡OCR数卖
"JRZQOCRY": &dto.JRZQOCRYReq{}, //银行卡OCR数据宝
"YYSY35TA": &dto.YYSY35TAReq{}, //运营商归属地数卖
"QYGLDJ12": &dto.QYGLDJ12Req{}, //企业年报信息核验
"FLXGDJG3": &dto.FLXGDJG3Req{}, //董监高司法综合信息核验
"QYGL8848": &dto.QYGLDJ12Req{}, //企业税收违法核查
"IVYZ18HY": &dto.IVYZ18HYReq{}, //婚姻状况核验V2单人
"IVYZ28HY": &dto.IVYZ28HYReq{}, //婚姻状况核验(单人)
"IVYZ38SR": &dto.IVYZ38SRReq{}, //婚姻状态核验(双人)
"IVYZ48SR": &dto.IVYZ48SRReq{}, //婚姻状态核验V2双人
"IVYZ5E22": &dto.IVYZ5E22Req{}, //双人婚姻评估查询zhicha版本
"DWBG5SAM": &dto.DWBG5SAMReq{}, //天远指迷报告
"QYGLDJ33": &dto.QYGLDJ33Req{}, //企业年报信息核验
"IVYZRAX1": &dto.IVYZRAX1Req{}, //融安信用分
"IVYZRAX2": &dto.IVYZRAX1Req{}, //融御反欺诈
"IVYZ2MN7": &dto.IVYZ2MN6Req{}, //学历Bzhicha
"FLXGHB4F": &dto.FLXGHB4FReq{}, //个人涉诉案件查询海宇API
"QYGLBH7Y": &dto.QYGLBH7YReq{}, //企业涉诉案件查询海宇API
"QYGL4YAB": &dto.QYGL4YABReq{}, //企业四要素认证shumai
"QYGL3YSB": &dto.QYGL3YSBReq{}, //企业三要素认证shumai
"QYGL2YSB": &dto.QYGL2YSBReq{}, //企业二要素认证shumai
"QYGLDG77": &dto.QYGLDG77Req{}, //企业对公打款认证shumai
"QCXGM4CL": &dto.QCXGM4CLReq{}, //名下车辆诺尔
"QYGLVR76": &dto.QYGLVR76Req{}, //名下企业诺尔
"JRZQV87M": &dto.JRZQV87MReq{}, //金融黑名单V110_c10
"JRZQV0MD": &dto.JRZQV0MDReq{}, //行为黑名单
"JRZQVT43": &dto.JRZQVT43Req{}, //投诉黑名单
"JRZQV3HM": &dto.JRZQV3HMReq{}, //债务逾期黑名单
"JRZQV4TF": &dto.JRZQV4TFReq{}, //智瞳分尊享版
"JRZQV5F4": &dto.JRZQV5F4Req{}, //风险变量V5F4
"JRZQV7MD": &dto.JRZQV7MDReq{}, //特殊名单
"IVYZVJJ6": &dto.IVYZVJJ6Req{}, //收入等级tax_income_level_v8
"YYSYTJC4": &dto.YYSYTJC4Req{}, //运营商近三个月欠费次数脉
"YYSYP72D": &dto.YYSYP72DReq{}, //运营商近三个月欠费次数脉
"DWBG3BF9": &dto.DWBG3BF9Req{}, // 个人风险档案max
"DWBG9FB3": &dto.DWBG9FB3Req{}, // 个人风险档案1
"DWBG9FB2": &dto.DWBG9FB2Req{}, // 个人风险档案2
"YYSYT7C4": &dto.YYSYT7C4Req{}, //手机停机验证
"YYSYXF7J": &dto.YYSYXF7JReq{}, //手机消费区间验证
"QCXG1S2L": &dto.QCXG1S2LReq{}, //车辆车五项信息核验V2
"QCXGA8V3": &dto.QCXGA8V3Req{}, //全国车辆配置查验(车五项+使用性质+承保)
} }
// 优先返回已配置的DTO // 优先返回已配置的DTO
@@ -335,6 +414,8 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
frontendRules = append(frontendRules, "日期格式") frontendRules = append(frontendRules, "日期格式")
case rule == "validAuthDate": case rule == "validAuthDate":
frontendRules = append(frontendRules, "授权日期格式") frontendRules = append(frontendRules, "授权日期格式")
case rule == "validDateRange":
frontendRules = append(frontendRules, "日期范围格式(YYYYMMDD-YYYYMMDD)")
case rule == "validTimeRange": case rule == "validTimeRange":
frontendRules = append(frontendRules, "时间范围格式") frontendRules = append(frontendRules, "时间范围格式")
case rule == "validMobileType": case rule == "validMobileType":
@@ -347,6 +428,10 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
frontendRules = append(frontendRules, "授权链接格式") frontendRules = append(frontendRules, "授权链接格式")
case rule == "validBase64Image": case rule == "validBase64Image":
frontendRules = append(frontendRules, "Base64图片格式JPG、BMP、PNG") frontendRules = append(frontendRules, "Base64图片格式JPG、BMP、PNG")
case rule == "base64" || rule == "validBase64":
frontendRules = append(frontendRules, "Base64编码格式支持图片/PDF")
case rule == "validBase64PDF":
frontendRules = append(frontendRules, "PDF文件的Base64编码仅PDF最大500KB")
case strings.HasPrefix(rule, "oneof="): case strings.HasPrefix(rule, "oneof="):
values := strings.TrimPrefix(rule, "oneof=") values := strings.TrimPrefix(rule, "oneof=")
frontendRules = append(frontendRules, "可选值: "+values) frontendRules = append(frontendRules, "可选值: "+values)
@@ -372,13 +457,15 @@ func (s *FormConfigServiceImpl) getFieldType(fieldType reflect.Type, validation
return "text" // time_range是HH:MM-HH:MM格式使用文本输入 return "text" // time_range是HH:MM-HH:MM格式使用文本输入
} else if strings.Contains(validation, "授权日期格式") { } else if strings.Contains(validation, "授权日期格式") {
return "text" // auth_date是YYYYMMDD-YYYYMMDD格式使用文本输入 return "text" // auth_date是YYYYMMDD-YYYYMMDD格式使用文本输入
} else if strings.Contains(validation, "日期范围格式") {
return "text" // date_range 为 YYYYMMDD-YYYYMMDD使用文本输入便于直接输入
} else if strings.Contains(validation, "日期") { } else if strings.Contains(validation, "日期") {
return "date" return "date"
} else if strings.Contains(validation, "链接") { } else if strings.Contains(validation, "链接") {
return "url" return "url"
} else if strings.Contains(validation, "可选值") { } else if strings.Contains(validation, "可选值") {
return "select" return "select"
} else if strings.Contains(validation, "Base64图片") || strings.Contains(validation, "base64") { } else if strings.Contains(validation, "Base64图片") || strings.Contains(validation, "Base64编码") || strings.Contains(validation, "base64") {
return "textarea" return "textarea"
} else if strings.Contains(validation, "图片地址") { } else if strings.Contains(validation, "图片地址") {
return "url" return "url"
@@ -397,50 +484,63 @@ func (s *FormConfigServiceImpl) getFieldType(fieldType reflect.Type, validation
func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string { func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
// 将下划线命名转换为中文标签 // 将下划线命名转换为中文标签
labelMap := map[string]string{ labelMap := map[string]string{
"mobile_no": "手机号码", "mobile_no": "手机号码",
"id_card": "身份证号", "id_card": "身份证号",
"name": "姓名", "idCard": "身份证号",
"man_name": "男方姓名", "name": "姓名",
"woman_name": "方姓名", "man_name": "方姓名",
"man_id_card": "男方身份证", "woman_name": "女方姓名",
"woman_id_card": "方身份证", "man_id_card": "方身份证",
"ent_name": "企业名称", "woman_id_card": "女方身份证",
"legal_person": "法人姓名", "ent_name": "企业名称",
"ent_code": "企业代码", "legal_person": "法人姓名",
"auth_date": "授权日期", "ent_code": "企业代码",
"time_range": "时间范围", "ent_reg_no": "企业注册号",
"authorized": "是否授权", "auth_date": "授权日期",
"authorization_url": "授权链接", "date_range": "日期范围",
"unique_id": "唯一标识", "time_range": "时间范围",
"return_url": "返回链接", "authorized": "是否授权",
"mobile_type": "手机类型", "authorization_url": "授权链接",
"start_date": "开始日期", "unique_id": "唯一标识",
"years": "年数", "return_url": "回调地址",
"bank_card": "银行卡号", "mobile_type": "手机类型",
"user_type": "关系类型", "start_date": "开始日期",
"vehicle_type": "车辆类型", "years": "年数",
"page_num": "页码", "bank_card": "银行卡号",
"page_size": "每页数量", "user_type": "关系类型",
"use_scenario": "使用场景", "vehicle_type": "车辆类型",
"auth_authorize_file_code": "授权文件编码", "page_num": "页码",
"plate_no": "车牌号", "page_size": "每页数量",
"plate_type": "号牌类型", "use_scenario": "使用场景",
"vin_code": "车辆识别代号VIN码", "auth_authorize_file_code": "授权文件编码",
"return_type": "返回类型", "plate_no": "车牌号",
"photo_data": "人脸图片", "plate_type": "号牌类型",
"owner_type": "企业主类型", "vin_code": "车辆识别代号VIN码",
"type": "查询类型", "return_type": "返回类型",
"query_reason_id": "查询原因ID", "photo_data": "入参图片base64编码",
"flag": "层次", "owner_type": "企业主类型",
"dir": "方向", "type": "查询类型",
"min_percent": "股权穿透比例下限", "query_reason_id": "查询原因ID",
"max_percent": "股权穿透比例上限", "flag": "层次",
"engine_number": "发动机号码", "dir": "方向",
"notice_model": "车辆型号", "min_percent": "股权穿透比例下限",
"vlphoto_data": "行驶证图片", "max_percent": "股权穿透比例上限",
"carplate_type": "车辆号牌类型", "engine_number": "发动机号码",
"image_url": "行驶证图片地址", "notice_model": "车辆型号",
"reg_url": "车辆登记证图片地址", "vlphoto_data": "行驶证图片",
"carplate_type": "车辆号牌类型",
"image_url": "入参图片地址",
"reg_url": "车辆登记证图片地址",
"token": "token采集及获取结果时所使用的凭证有效期2个小时在此时效内应用侧可以发起采集请求重复的采集所触发的结果会被忽略和结果查询",
"vehicle_name": "车型名称",
"vehicle_location": "车辆所在地",
"first_registrationdate": "首次登记日期",
"color": "颜色",
"plate_color": "车牌颜色",
"marital_type": "婚姻状况类型",
"auth_authorize_file_base64": "PDF授权文件Base64编码≤500KB仅PDF",
"account_no": "企业账户",
"account_bank": "开户行:如中国银行股份有限公司,查询失败请查看支持银行列表或使用主银行公司。",
} }
if label, exists := labelMap[jsonTag]; exists { if label, exists := labelMap[jsonTag]; exists {
@@ -454,48 +554,61 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
// generateExampleValue 生成示例值 // generateExampleValue 生成示例值
func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jsonTag string) string { func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jsonTag string) string {
exampleMap := map[string]string{ exampleMap := map[string]string{
"mobile_no": "13800138000", "mobile_no": "13800138000",
"id_card": "110101199001011234", "id_card": "110101199001011234",
"name": "张三", "idCard": "110101199001011234",
"man_name": "张三", "name": "张三",
"woman_name": "李四", "man_name": "张三",
"ent_name": "示例企业有限公司", "woman_name": "李四",
"legal_person": "王五", "ent_name": "示例企业有限公司",
"ent_code": "91110000123456789X", "legal_person": "王五",
"auth_date": "20240101-20241231", "ent_code": "91110000123456789X",
"time_range": "09:00-18:00", "ent_reg_no": "110000000123456",
"authorized": "1", "auth_date": "20240101-20241231",
"years": "5", "date_range": "20240101-20241231",
"bank_card": "6222021234567890123", "time_range": "09:00-18:00",
"mobile_type": "移动", "authorized": "1",
"start_date": "2024-01-01", "years": "5",
"unique_id": "UNIQUE123456", "bank_card": "6222021234567890123",
"return_url": "https://example.com/return", "mobile_type": "移动",
"authorization_url": "https://example.com/auth20250101.pdf 注意请不要使用示例链接示例链接仅作为参考格式。必须为实际的被查询人授权具有法律效益的授权书文件链接如访问不到或为不实授权书将追究责任。协议必须为http https", "start_date": "2024-01-01",
"user_type": "1", "unique_id": "UNIQUE123456",
"vehicle_type": "0", "return_url": "https://example.com/return ,回调地址链接用于接收回调数据。",
"page_num": "1", "authorization_url": "https://example.com/auth20250101.pdf 注意请不要使用示例链接示例链接仅作为参考格式。必须为实际的被查询人授权具有法律效益的授权书文件链接如访问不到或为不实授权书将追究责任。协议必须为http https",
"page_size": "10", "user_type": "1",
"use_scenario": "1", "vehicle_type": "0",
"auth_authorize_file_code": "AUTH123456", "page_num": "1",
"plate_no": "京A12345", "page_size": "10",
"plate_type": "01", "use_scenario": "1",
"vin_code": "LSGBF53M8DS123456", "auth_authorize_file_code": "AUTH123456",
"return_type": "1", "plate_no": "京A12345",
"photo_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "plate_type": "01",
"ownerType": "1", "vin_code": "LSGBF53M8DS123456",
"type": "per", "return_type": "1",
"query_reason_id": "1", "photo_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"flag": "4", "ownerType": "1",
"dir": "down", "type": "per",
"min_percent": "0", "query_reason_id": "1",
"max_percent": "1", "flag": "4",
"engine_number": "1234567890", "dir": "down",
"notice_model": "1", "min_percent": "0",
"vlphoto_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "max_percent": "1",
"carplate_type": "01", "engine_number": "1234567890",
"image_url": "https://example.com/images/driving_license.jpg", "notice_model": "1",
"reg_url": "https://example.com/images/vehicle_registration.jpg", "vlphoto_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"carplate_type": "01",
"image_url": "https://example.com/images/driving_license.jpg",
"reg_url": "https://example.com/images/vehicle_registration.jpg",
"token": "0fc79b80371f45e2ac1c693ef9136b24",
"vehicle_name": "车型名称,示例:凌派 2020款 锐·混动 1.5L 锐·舒适版",
"vehicle_location": "车辆所在地,示例:北京",
"first_registrationdate": "初登日期示例2020-05",
"color": "示例:白色",
"plate_color": "0",
"marital_type": "10",
"auth_authorize_file_base64": "JVBERi0xLjQKJcTl8uXr...示例PDF的Base64编码",
"account_no": "6222021234567890123",
"account_bank": "中国工商银行",
} }
if example, exists := exampleMap[jsonTag]; exists { if example, exists := exampleMap[jsonTag]; exists {
@@ -518,48 +631,61 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
// generatePlaceholder 生成占位符 // generatePlaceholder 生成占位符
func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType string) string { func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType string) string {
placeholderMap := map[string]string{ placeholderMap := map[string]string{
"mobile_no": "请输入11位手机号码", "mobile_no": "请输入11位手机号码",
"id_card": "请输入18位身份证号码", "id_card": "请输入18位身份证号码",
"name": "请输入真实姓名", "idCard": "请输入18位身份证号码",
"man_name": "请输入男方真实姓名", "name": "请输入真实姓名",
"woman_name": "请输入方真实姓名", "man_name": "请输入方真实姓名",
"ent_name": "请输入企业全称", "woman_name": "请输入女方真实姓名",
"legal_person": "请输入法人真实姓名", "ent_name": "请输入企业全称",
"ent_code": "请输入统一社会信用代码", "legal_person": "请输入法人真实姓名",
"auth_date": "请输入授权日期范围YYYYMMDD-YYYYMMDD", "ent_code": "请输入统一社会信用代码",
"time_range": "请输入时间范围HH:MM-HH:MM", "ent_reg_no": "请输入企业注册号(统一社会信用代码",
"authorized": "请选择是否授权", "auth_date": "请输入授权日期范围YYYYMMDD-YYYYMMDD",
"years": "请输入查询年数0-100", "date_range": "请输入日期范围YYYYMMDD-YYYYMMDD",
"bank_card": "请输入银行卡号", "time_range": "请输入时间范围HH:MM-HH:MM",
"mobile_type": "请选择手机类型", "authorized": "请选择是否授权",
"start_date": "请选择开始日期", "years": "请输入查询年数0-100",
"unique_id": "请输入唯一标识", "bank_card": "请输入银行卡号",
"return_url": "请输入返回链接", "mobile_type": "请选择手机类型",
"authorization_url": "请输入授权链接", "start_date": "请选择开始日期",
"user_type": "请选择关系类型", "unique_id": "请输入唯一标识",
"vehicle_type": "请选择车辆类型", "return_url": "请输入回调地址链接",
"page_num": "请输入页码", "authorization_url": "请输入授权链接",
"page_size": "请输入每页数量1-100", "user_type": "请选择关系类型",
"use_scenario": "请选择使用场景", "vehicle_type": "请选择车辆类型",
"auth_authorize_file_code": "请输入授权文件编码", "page_num": "请输入页码",
"plate_no": "请输入车牌号", "page_size": "请输入每页数量1-100",
"plate_type": "请选择号牌类型01或02", "use_scenario": "请选择使用场景",
"vin_code": "请输入17位车辆识别代号VIN码", "auth_authorize_file_code": "请输入授权文件编码",
"return_type": "请选择返回类型", "plate_no": "请输入车牌号",
"photo_data": "请输入base64编码的人脸图片支持JPG、BMP、PNG格式", "plate_type": "请选择号牌类型01或02",
"ownerType": "请选择企业主类型", "vin_code": "请输入17位车辆识别代号VIN码",
"type": "请选择查询类型", "return_type": "请选择返回类型",
"query_reason_id": "请选择查询原因ID", "photo_data": "请输入base64编码的入参图片支持JPG、BMP、PNG格式",
"flag": "请输入层次最大4", "ownerType": "请选择企业主类型",
"dir": "请选择方向up-向上down-向下)", "type": "请选择查询类型",
"min_percent": "请输入股权穿透比例下限默认0", "query_reason_id": "请选择查询原因ID",
"max_percent": "请输入股权穿透比例上限默认1", "flag": "请输入层次最大4",
"engine_number": "请输入发动机号码", "dir": "请选择方向up-向上down-向下)",
"notice_model": "请输入车辆型号", "min_percent": "请输入股权穿透比例下限默认0",
"vlphoto_data": "请输入行驶证图片", "max_percent": "请输入股权穿透比例上限默认1",
"carplate_type": "请选择车辆号牌类型01-大型汽车 02-小型汽车 03-使馆汽车 04-领馆汽车 05-境外汽车 06-外籍汽车 07-普通摩托车 08-轻便摩托车 09-使馆摩托车 10-领馆摩托车 11-境外摩托车 12-外籍摩托车 13-低速车 14-拖拉机 15-挂车 16-教练汽车 17-教练摩托车 20-临时入境汽车 21-临时入境摩托车 22-临时行驶车 23-警用汽车 24-警用摩托 51-新能源大型车 52-新能源小型车)", "engine_number": "请输入发动机号码",
"image_url": "请输入行驶证图片地址", "notice_model": "请输入车辆型号",
"reg_url": "请输入车辆登记证图片地址", "vlphoto_data": "请输入行驶证图片",
"carplate_type": "请选择车辆号牌类型01-大型汽车 02-小型汽车 03-使馆汽车 04-领馆汽车 05-境外汽车 06-外籍汽车 07-普通摩托车 08-轻便摩托车 09-使馆摩托车 10-领馆摩托车 11-境外摩托车 12-外籍摩托车 13-低速车 14-拖拉机 15-挂车 16-教练汽车 17-教练摩托车 20-临时入境汽车 21-临时入境摩托车 22-临时行驶车 23-警用汽车 24-警用摩托 51-新能源大型车 52-新能源小型车)",
"image_url": "请输入入参图片地址",
"reg_url": "请输入车辆登记证图片地址",
"token": "请输入token",
"vehicle_name": "请输入车型名称",
"vehicle_location": "请输入车辆所在地",
"first_registrationdate": "请输入首次登记日期格式YYYY-MM",
"color": "请输入颜色",
"plate_color": "请输入车牌颜色",
"marital_type": "请选择婚姻状况类型",
"auth_authorize_file_base64": "请输入PDF文件的Base64编码字符串",
"account_no": "请输入企业账户",
"account_bank": "请输入开户行:如中国银行股份有限公司,查询失败请查看支持银行列表或使用主银行公司。",
} }
if placeholder, exists := placeholderMap[jsonTag]; exists { if placeholder, exists := placeholderMap[jsonTag]; exists {
@@ -584,48 +710,61 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
// generateDescription 生成字段描述 // generateDescription 生成字段描述
func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation string) string { func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation string) string {
descMap := map[string]string{ descMap := map[string]string{
"mobile_no": "请输入11位手机号码", "mobile_no": "请输入11位手机号码",
"id_card": "请输入18位身份证号码最后一位如是字母请大写", "id_card": "请输入18位身份证号码最后一位如是字母请大写",
"name": "请输入真实姓名", "idCard": "请输入18位身份证号码最后一位如是字母请大写",
"man_name": "请输入男方真实姓名", "name": "请输入真实姓名",
"woman_name": "请输入方真实姓名", "man_name": "请输入方真实姓名",
"ent_name": "请输入企业全称", "woman_name": "请输入女方真实姓名",
"legal_person": "请输入法人真实姓名", "ent_name": "请输入企业全称",
"ent_code": "请输入统一社会信用代码", "legal_person": "请输入法人真实姓名",
"auth_date": "请输入授权日期范围格式YYYYMMDD-YYYYMMDD且日期范围必须包括今天", "ent_code": "请输入统一社会信用代码",
"time_range": "请输入时间范围格式HH:MM-HH:MM", "ent_reg_no": "请输入企业注册号(统一社会信用代码)",
"authorized": "请输入是否授权0-未授权1-已授权", "auth_date": "请输入授权日期范围格式YYYYMMDD-YYYYMMDD且日期范围必须包括今天",
"years": "请输入查询年数0-100", "date_range": "请输入日期范围格式YYYYMMDD-YYYYMMDD",
"bank_card": "请输入银行卡号", "time_range": "请输入时间范围格式HH:MM-HH:MM",
"mobile_type": "请选择手机类型", "authorized": "请输入是否授权0-未授权1-已授权",
"start_date": "请选择开始日期", "years": "请输入查询年数0-100",
"unique_id": "请输入唯一标识", "bank_card": "请输入银行卡号",
"return_url": "请输入返回链接", "mobile_type": "请选择手机类型",
"authorization_url": "请输入授权链接", "start_date": "请选择开始日期",
"user_type": "关系类型1-ETC开户人2-车辆所有人3-ETC经办人默认1-ETC开户人", "unique_id": "请输入唯一标识",
"vehicle_type": "车辆类型0-客车1-货车2-全部(默认查全部)", "return_url": "请输入回调地址链接",
"page_num": "请输入页码从1开始", "authorization_url": "请输入授权链接",
"page_size": "请输入每页数量范围1-100", "user_type": "关系类型1-ETC开户人2-车辆所有人3-ETC经办人默认1-ETC开户人",
"use_scenario": "使用场景1-信贷审核2-保险评估3-招聘背景调查4-其他业务场景99-其他", "vehicle_type": "车辆类型0-客车1-货车2-全部(默认查全部)",
"auth_authorize_file_code": "请输入授权文件编码", "page_num": "请输入页码从1开始",
"plate_no": "请输入车牌号", "page_size": "请输入每页数量范围1-100",
"plate_type": "号牌类型01-小型汽车02-大型汽车(可选)", "use_scenario": "使用场景1-信贷审核2-保险评估3-招聘背景调查4-其他业务场景99-其他",
"vin_code": "请输入17位车辆识别代号VIN码Vehicle Identification Number", "auth_authorize_file_code": "请输入授权文件编码",
"return_type": "返回类型1-专业和学校名称数据返回编码形式默认2-专业和学校名称数据返回中文名称", "plate_no": "请输入车牌号",
"photo_data": "人脸图片必填base64编码的图片数据仅支持JPG、BMP、PNG三种格式", "plate_type": "号牌类型01-小型汽车02-大型汽车(可选)",
"owner_type": "企业主类型编码1-法定代表人2-主要人员3-自然人股东4-法定代表人及自然人股东5-其他", "vin_code": "请输入17位车辆识别代号VIN码Vehicle Identification Number",
"type": "查询类型per-人员ent-企业 ", "return_type": "返回类型1-专业和学校名称数据返回编码形式默认2-专业和学校名称数据返回中文名称",
"query_reason_id": "查询原因ID1-授信审批2-贷中管理3-贷后管理4-异议处理5-担保查询6-租赁资质审查7-融资租赁审批8-借贷撮合查询9-保险审批10-资质审核11-风控审核12-企业背调", "photo_data": "入参图片base64编码的图片数据仅支持JPG、BMP、PNG三种格式",
"flag": "层次最大4", "owner_type": "企业主类型编码1-法定代表人2-主要人员3-自然人股东4-法定代表人及自然人股东5-其他",
"dir": "方向up-向上穿透down-向下穿透", "type": "查询类型per-人员ent-企业 ",
"min_percent": "股权穿透比例下限大于等于默认为0支持小数点后两位以小数指代百分比", "query_reason_id": "查询原因ID1-授信审批2-贷中管理3-贷后管理4-异议处理5-担保查询6-租赁资质审查7-融资租赁审批8-借贷撮合查询9-保险审批10-资质审核11-风控审核12-企业背调",
"max_percent": "股权穿透比例上限小于等于默认为1支持小数点后两位以小数指代百分比", "flag": "层次最大4",
"engine_number": "发动机号码", "dir": "方向up-向上穿透down-向下穿透",
"notice_model": "车辆型号", "min_percent": "股权穿透比例下限大于等于默认为0支持小数点后两位以小数指代百分比",
"vlphoto_data": "行驶证图片:base64编码的图片数据仅支持JPG、BMP、PNG三种格式", "max_percent": "股权穿透比例上限小于等于默认为1支持小数点后两位以小数指代百分比",
"carplate_type": "车辆号牌类型01-大型汽车02-小型汽车03-使馆汽车04-领馆汽车05-境外汽车06-外籍汽车07-普通摩托车08-轻便摩托车09-使馆摩托车10-领馆摩托车11-境外摩托车12-外籍摩托车13-低速车14-拖拉机15-挂车16-教练汽车17-教练摩托车20-临时入境汽车21-临时入境摩托车22-临时行驶车23-警用汽车24-警用摩托51-新能源大型车52-新能源小型车", "engine_number": "发动机号码",
"image_url": "行驶证图片地址必填请提供行驶证的图片URL地址", "notice_model": "车辆型号",
"reg_url": "车辆登记证图片地址非必填请提供车辆登记证的图片URL地址", "vlphoto_data": "行驶证图片:base64编码的图片数据仅支持JPG、BMP、PNG三种格式",
"carplate_type": "车辆号牌类型01-大型汽车02-小型汽车03-使馆汽车04-领馆汽车05-境外汽车06-外籍汽车07-普通摩托车08-轻便摩托车09-使馆摩托车10-领馆摩托车11-境外摩托车12-外籍摩托车13-低速车14-拖拉机15-挂车16-教练汽车17-教练摩托车20-临时入境汽车21-临时入境摩托车22-临时行驶车23-警用汽车24-警用摩托51-新能源大型车52-新能源小型车",
"image_url": "入参图片url地址",
"reg_url": "车辆登记证图片地址非必填请提供车辆登记证的图片URL地址",
"token": "token采集及获取结果时所使用的凭证有效期2个小时在此时效内应用侧可以发起采集请求重复的采集所触发的结果会被忽略和结果查询",
"vehicle_name": "车型名称,示例:凌派 2020款 锐·混动 1.5L 锐·舒适版",
"vehicle_location": "车辆所在地",
"first_registrationdate": "首次登记日期格式YYYY-MM",
"color": "颜色",
"plate_color": "车牌颜色",
"marital_type": "婚姻状况类型10-未登记无登记记录20-已婚30-丧偶40-离异",
"auth_authorize_file_base64": "请输入PDF文件的Base64编码字符串",
"account_no": "请输入企业账户",
"account_bank": "请输入开户行:如中国银行股份有限公司,查询失败请查看支持银行列表或使用主银行公司。",
} }
if desc, exists := descMap[jsonTag]; exists { if desc, exists := descMap[jsonTag]; exists {

View File

@@ -28,10 +28,10 @@ func TestFormConfigService_GetFormConfig(t *testing.T) {
// 验证字段信息 // 验证字段信息
expectedFields := map[string]bool{ expectedFields := map[string]bool{
"man_name": false, "man_name": false,
"man_id_card": false, "man_id_card": false,
"woman_name": false, "woman_name": false,
"woman_id_card": false, "woman_id_card": false,
} }
for _, field := range config.Fields { for _, field := range config.Fields {

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,350 @@
## 返回字段说明
# 5. 返回参数说明
## result.detail 参数说明Object
| 字段名 | 类型 | 说明 |
|---|---|---|
| lawsuitStat | object | 涉诉结果详见【lawsuitStat 参数说明】 |
| breachCaseList | array | 失信结果详见【breachCaseList 参数说明】 |
| consumptionRestrictionList | array | 限高结果详见【consumptionRestrictionList 参数说明】 |
---
## lawsuitStat 参数说明Object
### 一、统计概览count
| 字段名 | 类型 | 说明 |
|---|---|---|
| area_stat | string | 涉案地点分布 |
| ay_stat | string | 涉案案由分布 |
| jafs_stat | string | 结案方式分布 |
| larq_stat | string | 涉案时间分布 |
| count_total | string | 案件总数 |
| count_beigao | string | 被告总数 |
| count_yuangao | string | 原告总数 |
| count_other | string | 第三人总数 |
| count_jie_total | string | 已结案总数 |
| count_jie_beigao | string | 被告已结案总数 |
| count_jie_yuangao | string | 原告已结案总数 |
| count_jie_other | string | 第三人已结案总数 |
| count_wei_total | string | 未结案总数 |
| count_wei_beigao | string | 被告未结案总数 |
| count_wei_yuangao | string | 原告未结案总数 |
| count_wei_other | string | 第三人未结案总数 |
| money_total | string | 涉案总金额 |
| money_beigao | string | 被告金额 |
| money_yuangao | string | 原告金额 |
| money_other | string | 第三人金额 |
| money_jie_total | string | 已结案金额 |
| money_jie_beigao | string | 被告已结案金额 |
| money_jie_yuangao | string | 原告已结金额 |
| money_jie_other | string | 第三人已结案金额 |
| money_wei_total | string | 未结案金额 |
| money_wei_beigao | string | 被告未结案金额 |
| money_wei_yuangao | string | 原告未结案金额 |
| money_wei_other | string | 第三人未结案金额 |
| money_wei_percent | string | 未结案金额百分比 |
---
### 二、案件明细
#### 1. 行政案件administrative
| 字段名 | 类型 | 说明 |
|---|---|---|
| cases | array | 案件列表 |
| count | object | 统计信息(同 count 结构) |
**cases 子字段说明**
| 字段名 | 类型 | 说明 |
|---|---|---|
| c_ah | string | 案号 |
| c_ah_hx | string | 后续案号 |
| c_ah_ys | string | 原审案号 |
| c_gkws_dsr | string | 当事人 |
| c_gkws_glah | string | 相关案件号 |
| c_gkws_id | string | 公开文书 ID |
| c_gkws_pjjg | string | 判决结果 |
| c_slfsxx | string | 审理方式信息 |
| c_ssdy | string | 所属地域 |
| d_jarq | string | 结案时间 |
| d_larq | string | 立案时间 |
| n_ajbs | string | 案件标识 |
| n_ajjzjd | string | 案件进展阶段 |
| n_ajlx | string | 案件类型 |
| n_jaay | string | 结案案由 |
| n_jaay_tree | string | 结案案由详细 |
| n_jabdje | string | 结案标的金额 |
| n_jabdje_gj | string | 结案标的金额估计 |
| n_jabdje_gj_level | string | 结案标的金额估计等级(万元) |
| n_jabdje_level | string | 结案标的金额等级(万元) |
| n_jafs | string | 结案方式 |
| n_jbfy | string | 经办法院 |
| n_jbfy_cj | string | 法院所属层级 |
| n_laay | string | 立案案由 |
| n_laay_tree | string | 立案案由详细 |
| n_pj_victory | string | 胜诉估计 |
| n_qsbdje | string | 起诉标的金额 |
| n_qsbdje_level | string | 起诉标的金额等级 |
| n_slcx | string | 审理程序 |
| n_ssdw | string | 诉讼地位 |
| n_ssdw_ys | string | 一审诉讼地位 |
| c_dsrxx | array | 当事人信息列表 |
**c_dsrxx 子字段说明**
| 字段名 | 类型 | 说明 |
|---|---|---|
| n_ssdw | string | 诉讼地位 |
| c_mc | string | 名称 |
| n_dsrlx | string | 当事人类型 |
---
#### 2. 民事案件civil
> 字段结构与 **行政案件** 基本一致,新增以下字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| n_jaay_tag | string | 结案案由标签 |
| n_laay_tag | string | 立案案由标签 |
| n_qsbdje_gj | string | 起诉标的金额估计 |
| n_qsbdje_gj_level | string | 起诉标的金额估计等级 |
---
#### 3. 刑事案件criminal
| 字段名 | 类型 | 说明 |
|---|---|---|
| n_dzzm | string | 定罪罪名 |
| n_dzzm_tree | string | 定罪罪名详细 |
| n_fzje | string | 犯罪金额 |
| n_fzje_level | string | 犯罪金额等级 |
| n_ccxzxje | string | 财产刑执行金额 |
| n_ccxzxje_gj | string | 财产刑执行金额估计 |
| n_ccxzxje_gj_level | string | 财产刑执行金额估计等级 |
| n_pcjg | string | 判处结果 |
| n_pcpcje | string | 判处赔偿金额 |
| n_pcpcje_gj | string | 判处赔偿金额估计 |
| n_pcpcje_gj_level | string | 判处赔偿金额估计等级 |
| n_bqqpcje | string | 被请求赔偿金额 |
| n_bqqpcje_level | string | 被请求赔偿金额等级 |
---
#### 4. 执行案件implement
| 字段名 | 类型 | 说明 |
|---|---|---|
| n_sqzxbdje | string | 申请执行标的金额 |
| n_sjdwje | string | 实际到位金额 |
| n_wzxje | string | 未执行金额 |
---
#### 5. 非诉保全审查preservation
| 字段名 | 类型 | 说明 |
|---|---|---|
| c_sqbqbdw | string | 申请保全标的物 |
| n_sqbqse | string | 申请保全数额 |
| n_sqbqse_level | string | 申请保全数额等级 |
---
#### 6. 强制清算与破产案件bankrupt
> 字段结构与行政案件一致,主要用于破产/清算类案件。
---
### 三、案件串联树cases_tree
| 字段名 | 类型 | 说明 |
|---|---|---|
| administrative | array | 行政案件链 |
| civil | array | 民事案件链 |
| criminal | array | 刑事案件链 |
**案件节点说明**
| 字段名 | 类型 | 说明 |
|---|---|---|
| c_ah | string | 案号 |
| case_type | string | 案件类型 |
| n_ajbs | string | 案件标识 |
| stage_type | string | 审理阶段类型 |
| next | object | 后续关联案件 |
---
## breachCaseList 参数说明Array
| 字段名 | 类型 | 说明 |
|---|---|---|
| xb | string | 性别 |
| zzjgdm | string | 组织机构代码(企业查询时有值) |
| qyfr | string | 企业法人 |
| zxfy | string | 执行法院 |
| sf | string | 省份 |
| zxyjwh | string | 执行依据文号 |
| larq | string | 立案时间yyyy-mm-dd |
| ah | string | 案号 |
| zxyjdw | string | 执行依据单位 |
| yw | string | 生效法律文书确定的义务 |
| lxqk | string | 被执行人的履行情况 |
| xwqx | string | 失信被执行人行为具体情形 |
| fbrq | string | 发布时间yyyy-mm-dd |
| pjje_gj | string | 判决金额估计 |
---
## consumptionRestrictionList 参数说明Array
| 字段名 | 类型 | 说明 |
|---|---|---|
| qyfr | string | 企业法人 |
| qymc | string | 企业名称 |
| zxfy | string | 执行法院 |
| fbrq | string | 发布时间yyyy-mm-dd |
| larq | string | 立案时间yyyy-mm-dd |
| ah | string | 案号 |
---
## 字典说明
### 案件类型case_type
| 代码 | 说明 |
|---|---|
| 200 | 刑事案件 |
| 300 | 民事案件 |
| 400 | 行政案件 |
| 1000 | 执行案件 |
---
### 审理阶段stage_type
| 代码 | 说明 |
|---|---|
| 1 | 一审 |
| 2 | 二审 |
| 3 | 再审审查 |
| 4 | 再审 |
| 5 | 执行 |
---
### 金额等级(单位:万元)
| 代码 | 金额区间(万元) |
|---|---|
| 0 | 未填写或 0 |
| 1 | N < 1 |
| 2 | 1 N < 2 |
| 3 | 2 N < 3 |
| 4 | 3 N < 4 |
| 5 | 4 N < 5 |
| 6 | 5 N < 6 |
| 7 | 6 N < 7 |
| 8 | 7 N < 8 |
| 9 | 8 N < 9 |
| 10 | 9 N < 10 |
| 11 | 10 N < 20 |
| 12 | 20 N < 30 |
| 13 | 30 N < 40 |
| 14 | 40 N < 50 |
| 15 | 50 N < 60 |
| 16 | 60 N < 70 |
| 17 | 70 N < 80 |
| 18 | 80 N < 90 |
| 19 | 90 N < 100 |
| 20 | 100 N < 150 |
| 21 | 150 N < 200 |
| 22 | 200 N < 250 |
| 23 | 250 N < 300 |
| 24 | 300 N < 350 |
| 25 | 350 N < 400 |
| 26 | 400 N < 450 |
| 27 | 450 N < 500 |
| 28 | 500 N < 550 |
| 29 | 550 N < 600 |
| 30 | 600 N < 650 |
| 31 | 650 N < 700 |
| 32 | 700 N < 750 |
| 33 | 750 N < 800 |
| 34 | 800 N < 850 |
| 35 | 850 N < 900 |
| 36 | 900 N < 950 |
| 37 | 950 N < 1000 |
| 38 | 1000 N < 1500 |
| 39 | 1500 N < 2000 |
| 40 | 2000 N < 2500 |
| 41 | 2500 N < 3000 |
| 42 | 3000 N < 3500 |
| 43 | 3500 N < 4000 |
| 44 | 4000 N < 4500 |
| 45 | 4500 N < 5000 |
| 46 | 5000 N < 5500 |
| 47 | 5500 N < 6000 |
| 48 | 6000 N < 6500 |
| 49 | 6500 N < 7000 |
| 50 | 7000 N < 7500 |
| 51 | 7500 N < 8000 |
| 52 | 8000 N < 8500 |
| 53 | 8500 N < 9000 |
| 54 | 9000 N < 9500 |
| 55 | 9500 N < 10000 |
| 56 | 10000 N < 15000 |
| 57 | 15000 N < 20000 |
| 58 | 20000 N < 25000 |
| 59 | 25000 N < 30000 |
| 60 | 30000 N < 35000 |
| 61 | 35000 N < 40000 |
| 62 | 40000 N < 45000 |
| 63 | 45000 N < 50000 |
| 64 | 50000 N < 55000 |
| 65 | 55000 N < 60000 |
| 66 | 60000 N < 65000 |
| 67 | 65000 N < 70000 |
| 68 | 70000 N < 75000 |
| 69 | 75000 N < 80000 |
| 70 | 80000 N < 85000 |
| 71 | 85000 N < 90000 |
| 72 | 90000 N < 95000 |
| 73 | 95000 N < 100000 |
| 74 | 100000 N < 150000 |
| 75 | 150000 N < 200000 |
| 76 | 200000 N < 250000 |
| 77 | 250000 N < 300000 |
| 78 | 300000 N < 350000 |
| 79 | 350000 N < 400000 |
| 80 | 400000 N < 450000 |
| 81 | 450000 N < 500000 |
| 82 | 500000 N < 550000 |
| 83 | 550000 N < 600000 |
| 84 | 600000 N < 650000 |
| 85 | 650000 N < 700000 |
| 86 | 700000 N < 750000 |
| 87 | 750000 N < 800000 |
| 88 | 800000 N < 850000 |
| 89 | 850000 N < 900000 |
| 90 | 900000 N < 950000 |
| 91 | 950000 N < 1000000 |
| 92 | 1000000 N < 1500000 |
| 93 | 1500000 N < 2000000 |
| 94 | 2000000 N < 2500000 |
| 95 | 2500000 N < 3000000 |
| 96 | 3000000 N < 3500000 |
| 97 | 3500000 N < 4000000 |
| 98 | 4000000 N < 4500000 |
| 99 | 4500000 N < 5000000 |
| 100 | 5000000 N |

View File

@@ -0,0 +1,137 @@
{
"sxbzxr": [],
"xgbzxr": [],
"entout": {
"jurisdict": {
"cases": [],
"count": {}
},
"preservation": {
"cases": [],
"count": {
"money_jie_total": "11",
"count_total": "1",
"larq_stat": "2025(1)",
"money_wei_percent": "0",
"area_stat": "重庆市(1)",
"money_jie_beigao": "0",
"count_jie_total": "1",
"money_jie_other": "0",
"count_wei_total": "0",
"money_yuangao": "11",
"count_jie_beigao": "0",
"money_beigao": "0",
"ay_stat": "非讼程序案件案由(1)",
"count_wei_other": "0",
"count_wei_beigao": "0",
"count_wei_yuangao": "0",
"money_other": "0",
"count_yuangao": "1",
"money_wei_yuangao": "0",
"money_jie_yuangao": "11",
"money_wei_beigao": "0",
"count_jie_yuangao": "1",
"count_other": "0",
"count_jie_other": "0",
"count_beigao": "0",
"money_wei_total": "0",
"money_wei_other": "0",
"money_total": "11",
"jafs_stat": "转为其他案件中的保全(1)"
}
},
"cases_tree": {},
"administrative": {
"cases": [],
"count": {}
},
"civil": {
"cases": [
{
"n_ssdw": "原告",
"n_ccxzxje": "",
"n_pcjg": "",
"n_sqbqbdw": "",
"c_gkws_glah": "",
"n_sqbqse_level": "",
"n_pj_victory": "未知",
"n_wzxje": "",
"c_gkws_dsr": "",
"n_qsbdje_gj_level": "",
"n_jbfy_cj": "基层法院",
"n_qsbdje": "14858.43",
"n_slcx": "一审",
"n_ajjzjd": "已结案",
"n_jbfy": "淮南市田家庵区人民法院",
"c_gkws_id": "",
"d_jarq": "2023-03-29",
"n_jabdje_level": "2",
"c_slfsxx": "1,2023-03-27 15:00:00,淮南市田家庵区法院6号法庭,1",
"n_ccxzxje_gj_level": "",
"n_pcpcje": "",
"n_sjdwje": "",
"n_fzje_level": "",
"n_pcpcje_level": "",
"c_ah": "(2023)皖0403民初1229号",
"n_qsbdje_level": "2",
"c_ssdy": "安徽省",
"n_jabdje": "14460.43",
"n_ajlx": "民事一审",
"n_laay": "合同、准合同纠纷",
"n_jaay": "合同、准合同纠纷",
"n_laay_tree": "合同、准合同纠纷,合同纠纷,租赁合同纠纷",
"n_bqqpcje": "",
"n_jabdje_gj": "",
"n_jaay_tree": "合同、准合同纠纷,合同纠纷,租赁合同纠纷",
"n_pcpcje_gj": "",
"n_jabdje_gj_level": "",
"n_ccxzxje_level": "",
"n_ssdw_ys": "原告",
"n_pcpcje_gj_level": "",
"n_jafs": "判决",
"c_id": "71542222",
"n_qsbdje_gj": "",
"c_ah_hx": "(2023)皖0403执3847号:c996fda58ff2552d6ba834603c2e9e63",
"n_fzje": "",
"n_ccxzxje_gj": "",
"n_sqbqse": "",
"n_dzzm": "",
"n_bqqpcje_level": "",
"c_gkws_pjjg": "",
"n_laay_tag": "",
"n_jaay_tag": "",
"n_ajbs": "b1802615be1d2dd9236192322b247c6d",
"n_dzzm_tree": "",
"n_sqzxbdje": "",
"c_ah_ys": "",
"c_dsrxx": [
{
"n_ssdw": "原告",
"c_mc": "xxxxxx",
"n_dsrlx": "企业组织"
},
{
"n_ssdw": "被告",
"c_mc": "xxx",
"n_dsrlx": "自然人"
}
],
"d_larq": "2023-02-06"
}
],
"count": {}
},
"criminal": {
"cases": [],
"count": {}
},
"compensate": {
"cases": [],
"count": {}
},
"bankrupt": {
"cases": [],
"count": {}
}
}
}

View File

@@ -0,0 +1,68 @@
## 返回字段说明
| 名称 | 说明 | 备注 |
|--------|--------------------|------|
| sxbzxr | 失信被执行人 | |
| entout | 涉诉信息 | |
| xgbzxr | 限高被执行人 | |
### sxbzxr参数
| 名称 | 说明 | 备注 |
|------------------|------------------------------|------|
| yw | 生效法律文书确定的义务 | |
| pjje_gj | 判决金额_估计 | |
| xwqx | 失信被执行人行为具体情形 | |
| id | 标识 | |
| zxfy | 执行法院 | |
| ah | 案号 | |
| zxyjwh | 执行依据文号 | |
| lxqk | 被执行人的履行情况 | |
| zxyjdw | 出执行依据单位 | |
| fbrq | 发布时间(日期) | |
| xb | 性别 | |
| larq | 立案日期 | |
| sf | 省份 | |
### entout参数
| 名称 | 说明 | 备注 |
|--------------------------------|------------------------------|------|
| civil | 案件名称 | 返回有案件的信息,如:<br>administrative/行政案<br>implement/执行案件<br>count/统计<br>preservation/非诉保全审查<br>crc/当事人变更码<br>civil/民事案件<br>criminal/刑事案件<br>cases_tree/串联树<br>bankrupt/强制清算与破产案件<br>jurisdict/管辖案件<br>compensate/赔偿案件 |
| cases | 案件 | |
| n_jabdje_gj_level | 结案标的金额估计等级 | |
| n_jbfy_cj | 法院所属层级 | |
| c_gkws_glah | 相关案件号 | |
| n_jafs | 结案方式 | |
| n_sjdwje | 实际到位金额 | |
| n_sqzxbdje | 申请执行标的金额 | |
| n_wzxje | 未执行金额 | |
| n_sqbqse | 申请保全数额 | |
| n_sqbqse_level | 申请保全数额等级 | |
| n_jaay_tag | 结案案由标签 | |
| n_qsbdje_gj_level | 起诉标的金额估计等级 | |
| n_laay_tag | 立案案由标签 | |
| n_pcpcje_level | 判处赔偿金额等级 | |
| n_bqqpcje | 被请求赔偿金额 | |
| n_pcpcje_gj_level | 判处赔偿金额估计等级 | |
| n_dzzm | 定罪罪名 | |
| n_ccxzxje_level | 财产刑执行金额等级 | |
| n_ccxzxje_gj_level | 财产刑执行金额估计等级 | |
| n_fzje | 犯罪金额 | |
| n_pcpcje | 判处赔偿金额 | |
| n_dzzm_tree | 定罪罪名树 | |
| n_pcjg | 判处结果 | |
| n_ccxzxje | 财产刑执行金额 | |
| n_fzje_level | 犯罪金额等级 | |
| n_ccxzxje_gj | 财产刑执行金额估计 | |
| n_bqqpcje_level | 被请求赔偿金额等级 | |
| n_ssdw | 诉讼地位 | |
| d_jarq | 结案时间 | |
| c_gkws_pjjg | 判决结果 | |
| n_qsbdje | 起诉标的金额 | |
| n_crc | 案件变更码 | |
| c_ssdy | 所属地域 | |
| n_ajjzjd | 案件进展阶段 | |
| n_jaay | 结案案由 | |
| n_ajlx | 案件类型 | |
| c_ah_ys | 原审案号 | |
| n_laay_tree | 立案案由详细 | |
| n_ajbs | 案件

View File

@@ -32,6 +32,12 @@ func (cs *CombService) RegisterProcessor(apiCode string, processor processors.Pr
cs.processorRegistry[apiCode] = processor cs.processorRegistry[apiCode] = processor
} }
// GetProcessor 获取处理器(用于内部调用)
func (cs *CombService) GetProcessor(apiCode string) (processors.ProcessorFunc, bool) {
processor, exists := cs.processorRegistry[apiCode]
return processor, exists
}
// ProcessCombRequest 处理组合包请求 - 实现 CombServiceInterface // ProcessCombRequest 处理组合包请求 - 实现 CombServiceInterface
func (cs *CombService) ProcessCombRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies, packageCode string) (*processors.CombinedResult, error) { func (cs *CombService) ProcessCombRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies, packageCode string) (*processors.CombinedResult, error) {
// 1. 根据组合包code获取产品信息 // 1. 根据组合包code获取产品信息

View File

@@ -48,6 +48,7 @@ type baseProductData struct {
RiskWarning riskWarning `json:"riskWarning"` RiskWarning riskWarning `json:"riskWarning"`
StandLiveInfo standLiveInfo `json:"standLiveInfo"` StandLiveInfo standLiveInfo `json:"standLiveInfo"`
VerifyRule string `json:"verifyRule"` VerifyRule string `json:"verifyRule"`
MultCourtInfo multCourtInfo `json:"multCourtInfo"`
} }
// baseInfo 存放被查询人的基础身份信息 // baseInfo 存放被查询人的基础身份信息
@@ -171,6 +172,34 @@ type standLiveInfo struct {
FinalAuthResult string `json:"finalAuthResult"` FinalAuthResult string `json:"finalAuthResult"`
} }
// multCourtInfo 司法风险核验产品,统一承载涉案/执行/失信/限高四类公告
type multCourtInfo struct {
LegalCasesFlag int `json:"legalCasesFlag"`
LegalCases []multCaseItem `json:"legalCases"`
ExecutionCasesFlag int `json:"executionCasesFlag"`
ExecutionCases []multCaseItem `json:"executionCases"`
DisinCasesFlag int `json:"disinCasesFlag"`
DisinCases []multCaseItem `json:"disinCases"`
LimitCasesFlag int `json:"limitCasesFlag"`
LimitCases []multCaseItem `json:"limitCases"`
}
// multCaseItem 司法各类公告的通用记录结构
type multCaseItem struct {
CaseNumber string `json:"caseNumber"`
CaseType string `json:"caseType"`
Court string `json:"court"`
LitigantType string `json:"litigantType"`
FilingTime string `json:"filingTime"`
DisposalTime string `json:"disposalTime"`
CaseStatus string `json:"caseStatus"`
ExecutionAmount string `json:"executionAmount"`
RepaidAmount string `json:"repaidAmount"`
CaseReason string `json:"caseReason"`
DisposalMethod string `json:"disposalMethod"`
JudgmentResult string `json:"judgmentResult"`
}
// --- FLXG7E8F --- // --- FLXG7E8F ---
// judicialProductData 对应 FLXG7E8F 司法产品数据 // judicialProductData 对应 FLXG7E8F 司法产品数据
@@ -702,52 +731,60 @@ func buildBasicInfo(ctx context.Context, sourceCtx *sourceContext) reportBasicIn
Details: carrierDetails, Details: carrierDetails,
}) })
// 兼容处理:安全访问JudicialData // 兼容处理:从DWBG8B4D.multCourtInfo获取司法记录条数并按案件类型拆分
var stat *lawsuitStat
if sourceCtx != nil && sourceCtx.JudicialData != nil {
stat = &sourceCtx.JudicialData.JudicialData.LawsuitStat
}
totalCaseCount := 0
totalCriminal := 0
totalExecution := 0 totalExecution := 0
if stat != nil {
// 兼容处理安全访问Cases数组
totalCriminal = safeLen(stat.Criminal.Cases)
totalCaseCount = totalCriminal + safeLen(stat.Civil.Cases) + safeLen(stat.Administrative.Cases) + safeLen(stat.Preservation.Cases) + safeLen(stat.Bankrupt.Cases)
totalExecution = safeLen(stat.Implement.Cases)
}
totalDishonest := 0 totalDishonest := 0
totalRestriction := 0 totalRestriction := 0
if sourceCtx != nil && sourceCtx.JudicialData != nil { criminalCount := 0
totalDishonest = safeLen(sourceCtx.JudicialData.JudicialData.BreachCaseList) civilCount := 0
totalRestriction = safeLen(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList) administrativeCount := 0
preservationCount := 0
bankruptCount := 0
if sourceCtx != nil && sourceCtx.BaseData != nil {
mc := sourceCtx.BaseData.MultCourtInfo
totalExecution = safeLen(mc.ExecutionCases)
totalDishonest = safeLen(mc.DisinCases)
totalRestriction = safeLen(mc.LimitCases)
for _, c := range mc.LegalCases {
switch strings.TrimSpace(c.CaseType) {
case "刑事案件":
criminalCount++
case "民事案件":
civilCount++
case "行政案件":
administrativeCount++
case "保全审查":
preservationCount++
case "破产清算":
bankruptCount++
default:
// 其它类型暂不单独展示,只参与是否有司法记录的判断
civilCount++
}
}
} }
if totalCaseCount > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 { totalLegal := criminalCount + civilCount + administrativeCount + preservationCount + bankruptCount
detailParts := make([]string, 0, 5)
if stat != nil { if totalLegal > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
addCaseDetail := func(label string, count int) { detailParts := make([]string, 0, 8)
if count > 0 { addCaseDetail := func(label string, count int) {
detailParts = append(detailParts, fmt.Sprintf("%s%d条", label, count)) if count > 0 {
} detailParts = append(detailParts, fmt.Sprintf("%s%d条", label, count))
} }
addCaseDetail("刑事案件", safeLen(stat.Criminal.Cases))
addCaseDetail("民事案件", safeLen(stat.Civil.Cases))
addCaseDetail("行政案件", safeLen(stat.Administrative.Cases))
addCaseDetail("非诉保全审查案件", safeLen(stat.Preservation.Cases))
addCaseDetail("强制清算与破产案件", safeLen(stat.Bankrupt.Cases))
}
if totalExecution > 0 {
detailParts = append(detailParts, fmt.Sprintf("执行案件%d条", totalExecution))
}
if totalDishonest > 0 {
detailParts = append(detailParts, fmt.Sprintf("失信案件%d条", totalDishonest))
}
if totalRestriction > 0 {
detailParts = append(detailParts, fmt.Sprintf("限高案件%d条", totalRestriction))
} }
addCaseDetail("刑事案件", criminalCount)
addCaseDetail("民事案件", civilCount)
addCaseDetail("行政案件", administrativeCount)
addCaseDetail("保全审查案件", preservationCount)
addCaseDetail("强制清算与破产案件", bankruptCount)
addCaseDetail("执行案件", totalExecution)
addCaseDetail("失信案件", totalDishonest)
addCaseDetail("限高案件", totalRestriction)
details := buildCaseDetails(detailParts) details := buildCaseDetails(detailParts)
verifications = append(verifications, verificationItem{ verifications = append(verifications, verificationItem{
Item: "法院信息", Item: "法院信息",
@@ -821,50 +858,78 @@ func buildRiskIdentification(ctx context.Context, sourceCtx *sourceContext) risk
}, },
} }
// 兼容处理:安全访问JudicialData // 兼容处理:从DWBG8B4D.multCourtInfo获取司法信息
if sourceCtx == nil || sourceCtx.JudicialData == nil { if sourceCtx == nil || sourceCtx.BaseData == nil {
log.Debug("JudicialData为空返回空的风险识别数据", log.Debug("BaseData为空返回空的风险识别数据",
zap.String("api_code", "COMBHZY2"), zap.String("api_code", "COMBHZY2"),
) )
return identification return identification
} }
mc := sourceCtx.BaseData.MultCourtInfo
stat := sourceCtx.JudicialData.JudicialData.LawsuitStat
baseName := "" baseName := ""
baseID := "" baseID := ""
if sourceCtx.BaseData != nil { baseName = sourceCtx.BaseData.BaseInfo.Name
baseName = sourceCtx.BaseData.BaseInfo.Name baseID = sourceCtx.BaseData.BaseInfo.IdCard
baseID = sourceCtx.BaseData.BaseInfo.IdCard
}
// 兼容处理:安全访问Cases数组 // 涉案公告列表直接使用multCourtInfo.legalCases
caseRecords := make([]caseAnnouncementRecord, 0) caseRecords := make([]caseAnnouncementRecord, 0)
if stat.Civil.Cases != nil { for _, c := range mc.LegalCases {
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Civil.Cases, "民事案件")...) record := caseAnnouncementRecord{
} CaseNumber: defaultIfEmpty(c.CaseNumber, "-"),
if stat.Criminal.Cases != nil { CaseType: defaultIfEmpty(c.CaseType, "-"),
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Criminal.Cases, "刑事案件")...) FilingDate: defaultIfEmpty(c.FilingTime, ""),
} Authority: defaultIfEmpty(c.Court, ""),
if stat.Administrative.Cases != nil { }
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Administrative.Cases, "行政案件")...) caseRecords = append(caseRecords, record)
}
if stat.Preservation.Cases != nil {
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Preservation.Cases, "非诉保全审查")...)
}
if stat.Bankrupt.Cases != nil {
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Bankrupt.Cases, "强制清算与破产")...)
} }
identification.CaseAnnouncements.Records = caseRecords identification.CaseAnnouncements.Records = caseRecords
if stat.Implement.Cases != nil { // 执行公告列表multCourtInfo.executionCases
identification.EnforcementAnnouncements.Records = convertEnforcementAnnouncements(stat.Implement.Cases) enfRecords := make([]enforcementAnnouncementRecord, 0, len(mc.ExecutionCases))
for _, c := range mc.ExecutionCases {
amountStr := strings.TrimSpace(c.ExecutionAmount)
targetAmount := "-"
if amountStr != "" && amountStr != "-" {
targetAmount = formatCurrencyYuan(parseFloatSafe(amountStr))
}
record := enforcementAnnouncementRecord{
CaseNumber: defaultIfEmpty(c.CaseNumber, "-"),
TargetAmount: targetAmount,
FilingDate: defaultIfEmpty(c.FilingTime, ""),
Court: defaultIfEmpty(c.Court, ""),
Status: defaultIfEmpty(c.CaseStatus, "-"),
}
enfRecords = append(enfRecords, record)
} }
if sourceCtx.JudicialData.JudicialData.BreachCaseList != nil { identification.EnforcementAnnouncements.Records = enfRecords
identification.DishonestAnnouncements.Records = convertDishonestAnnouncements(sourceCtx.JudicialData.JudicialData.BreachCaseList, baseName, baseID)
// 失信公告列表multCourtInfo.disinCases
dishonestRecords := make([]dishonestAnnouncementRecord, 0, len(mc.DisinCases))
for _, item := range mc.DisinCases {
record := dishonestAnnouncementRecord{
DishonestPerson: defaultIfEmpty(baseName, "-"),
IdCard: defaultIfEmpty(baseID, "-"),
Court: defaultIfEmpty(item.Court, ""),
FilingDate: defaultIfEmpty(item.FilingTime, item.DisposalTime),
PerformanceStatus: defaultIfEmpty(item.JudgmentResult, defaultIfEmpty(item.CaseStatus, "-")),
}
dishonestRecords = append(dishonestRecords, record)
} }
if sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList != nil { identification.DishonestAnnouncements.Records = dishonestRecords
identification.HighConsumptionRestrictionAnn.Records = convertConsumptionRestrictions(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList, baseName, baseID)
// 限高公告列表multCourtInfo.limitCases
limitRecords := make([]highRestrictionAnnouncementRecord, 0, len(mc.LimitCases))
for _, item := range mc.LimitCases {
record := highRestrictionAnnouncementRecord{
RestrictedPerson: defaultIfEmpty(baseName, "-"),
IdCard: defaultIfEmpty(baseID, "-"),
Court: defaultIfEmpty(item.Court, ""),
StartDate: defaultIfEmpty(item.FilingTime, item.DisposalTime),
Measure: "限制高消费",
}
limitRecords = append(limitRecords, record)
} }
identification.HighConsumptionRestrictionAnn.Records = limitRecords
return identification return identification
} }
@@ -1299,13 +1364,13 @@ func gatherOtherRiskDetails(sourceCtx *sourceContext) string {
if risk.VeryFrequentRentalApplications > 0 { if risk.VeryFrequentRentalApplications > 0 {
hits = append(hits, "租赁机构申请次数极多") hits = append(hits, "租赁机构申请次数极多")
} }
// 兼容处理:安全访问JudicialData // 兼容处理:根据DWBG8B4D.multCourtInfo判断是否存在司法记录
if sourceCtx != nil && sourceCtx.JudicialData != nil { if sourceCtx != nil && sourceCtx.BaseData != nil {
stat := sourceCtx.JudicialData.JudicialData.LawsuitStat mc := sourceCtx.BaseData.MultCourtInfo
totalCase := safeLen(stat.Civil.Cases) + safeLen(stat.Criminal.Cases) + safeLen(stat.Administrative.Cases) + safeLen(stat.Preservation.Cases) + safeLen(stat.Bankrupt.Cases) totalCase := safeLen(mc.LegalCases)
totalExecution := safeLen(stat.Implement.Cases) totalExecution := safeLen(mc.ExecutionCases)
totalDishonest := safeLen(sourceCtx.JudicialData.JudicialData.BreachCaseList) totalDishonest := safeLen(mc.DisinCases)
totalRestriction := safeLen(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList) totalRestriction := safeLen(mc.LimitCases)
if totalCase > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 { if totalCase > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
hits = append(hits, "存在司法风险记录") hits = append(hits, "存在司法风险记录")
} }
@@ -1393,11 +1458,10 @@ func buildRuleHitBullet(summary reportSummary) (string, bool, bool) {
} }
func buildJudicialBullet(ctx *sourceContext) (string, bool, bool) { func buildJudicialBullet(ctx *sourceContext) (string, bool, bool) {
if ctx.JudicialData == nil { if ctx == nil || ctx.BaseData == nil {
return "", false, false return "", false, false
} }
mc := ctx.BaseData.MultCourtInfo
stat := ctx.JudicialData.JudicialData.LawsuitStat
parts := make([]string, 0, 6) parts := make([]string, 0, 6)
addPart := func(label string, count int) { addPart := func(label string, count int) {
@@ -1406,12 +1470,10 @@ func buildJudicialBullet(ctx *sourceContext) (string, bool, bool) {
} }
} }
addPart("刑事案件", len(stat.Criminal.Cases)) addPart("涉案公告", len(mc.LegalCases))
addPart("民事案件", len(stat.Civil.Cases)) addPart("执行案件", len(mc.ExecutionCases))
addPart("行政案件", len(stat.Administrative.Cases)) addPart("失信记录", len(mc.DisinCases))
addPart("执行案件", len(stat.Implement.Cases)) addPart("限高记录", len(mc.LimitCases))
addPart("失信记录", len(ctx.JudicialData.JudicialData.BreachCaseList))
addPart("限高记录", len(ctx.JudicialData.JudicialData.ConsumptionRestrictionList))
if len(parts) == 0 { if len(parts) == 0 {
return "", false, false return "", false, false

View File

@@ -2,10 +2,17 @@ package processors
import ( import (
"context" "context"
"tyapi-server/internal/application/api/commands" "tyapi-server/internal/application/api/commands"
"tyapi-server/internal/domains/api/repositories"
"tyapi-server/internal/infrastructure/external/alicloud" "tyapi-server/internal/infrastructure/external/alicloud"
"tyapi-server/internal/infrastructure/external/muzi" "tyapi-server/internal/infrastructure/external/huibo"
"tyapi-server/internal/infrastructure/external/jiguang" "tyapi-server/internal/infrastructure/external/jiguang"
"tyapi-server/internal/infrastructure/external/muzi"
"tyapi-server/internal/infrastructure/external/nuoer"
"tyapi-server/internal/infrastructure/external/shujubao"
"tyapi-server/internal/infrastructure/external/shumai"
"tyapi-server/internal/infrastructure/external/haiyuapi"
"tyapi-server/internal/infrastructure/external/tianyancha" "tyapi-server/internal/infrastructure/external/tianyancha"
"tyapi-server/internal/infrastructure/external/westdex" "tyapi-server/internal/infrastructure/external/westdex"
"tyapi-server/internal/infrastructure/external/xingwei" "tyapi-server/internal/infrastructure/external/xingwei"
@@ -27,6 +34,7 @@ type CallContext struct {
// ProcessorDependencies 处理器依赖容器 // ProcessorDependencies 处理器依赖容器
type ProcessorDependencies struct { type ProcessorDependencies struct {
WestDexService *westdex.WestDexService WestDexService *westdex.WestDexService
ShujubaoService *shujubao.ShujubaoService
MuziService *muzi.MuziService MuziService *muzi.MuziService
YushanService *yushan.YushanService YushanService *yushan.YushanService
TianYanChaService *tianyancha.TianYanChaService TianYanChaService *tianyancha.TianYanChaService
@@ -34,15 +42,29 @@ type ProcessorDependencies struct {
ZhichaService *zhicha.ZhichaService ZhichaService *zhicha.ZhichaService
XingweiService *xingwei.XingweiService XingweiService *xingwei.XingweiService
JiguangService *jiguang.JiguangService JiguangService *jiguang.JiguangService
ShumaiService *shumai.ShumaiService
HuiboService *huibo.HuiboService
NuoerService *nuoer.NuoerService
HaiyuapiService *haiyuapi.HaiyuapiService
Validator interfaces.RequestValidator Validator interfaces.RequestValidator
CombService CombServiceInterface // Changed to interface to break import cycle CombService CombServiceInterface // Changed to interface to break import cycle
Options *commands.ApiCallOptions // 添加Options支持 Options *commands.ApiCallOptions // 添加Options支持
CallContext *CallContext // 添加CallApi调用上下文 CallContext *CallContext // 添加CallApi调用上下文
// 企业报告记录仓储,用于持久化 QYGLJ1U9 生成的企业报告
ReportRepo repositories.ReportRepository
// 企业报告 PDF 异步预生成(可为 nil
ReportPDFScheduler QYGLReportPDFScheduler
// APIPublicBaseURL 对外 API 根地址(无尾斜杠),用于 QYGL reportUrl 等
APIPublicBaseURL string
} }
// NewProcessorDependencies 创建处理器依赖容器 // NewProcessorDependencies 创建处理器依赖容器
func NewProcessorDependencies( func NewProcessorDependencies(
westDexService *westdex.WestDexService, westDexService *westdex.WestDexService,
shujubaoService *shujubao.ShujubaoService,
muziService *muzi.MuziService, muziService *muzi.MuziService,
yushanService *yushan.YushanService, yushanService *yushan.YushanService,
tianYanChaService *tianyancha.TianYanChaService, tianYanChaService *tianyancha.TianYanChaService,
@@ -50,22 +72,37 @@ func NewProcessorDependencies(
zhichaService *zhicha.ZhichaService, zhichaService *zhicha.ZhichaService,
xingweiService *xingwei.XingweiService, xingweiService *xingwei.XingweiService,
jiguangService *jiguang.JiguangService, jiguangService *jiguang.JiguangService,
shumaiService *shumai.ShumaiService,
huiboService *huibo.HuiboService,
nuoerService *nuoer.NuoerService,
haiyuapiService *haiyuapi.HaiyuapiService,
validator interfaces.RequestValidator, validator interfaces.RequestValidator,
combService CombServiceInterface, // Changed to interface combService CombServiceInterface, // Changed to interface
reportRepo repositories.ReportRepository,
reportPDFScheduler QYGLReportPDFScheduler,
apiPublicBaseURL string,
) *ProcessorDependencies { ) *ProcessorDependencies {
return &ProcessorDependencies{ return &ProcessorDependencies{
WestDexService: westDexService, WestDexService: westDexService,
MuziService: muziService, ShujubaoService: shujubaoService,
YushanService: yushanService, MuziService: muziService,
TianYanChaService: tianYanChaService, YushanService: yushanService,
AlicloudService: alicloudService, TianYanChaService: tianYanChaService,
ZhichaService: zhichaService, AlicloudService: alicloudService,
XingweiService: xingweiService, ZhichaService: zhichaService,
JiguangService: jiguangService, XingweiService: xingweiService,
Validator: validator, JiguangService: jiguangService,
CombService: combService, ShumaiService: shumaiService,
Options: nil, // 初始化为nil在调用时设置 HuiboService: huiboService,
CallContext: nil, // 初始化为nil在调用时设置 NuoerService: nuoerService,
HaiyuapiService: haiyuapiService,
Validator: validator,
CombService: combService,
Options: nil, // 初始化为nil在调用时设置
CallContext: nil, // 初始化为nil在调用时设置
ReportRepo: reportRepo,
ReportPDFScheduler: reportPDFScheduler,
APIPublicBaseURL: apiPublicBaseURL,
} }
} }

View File

@@ -0,0 +1,33 @@
|
| 产品编码 | 产品名称 | 价格 |
| :--- | :--- | :--- |
|| mobile4Verify | 运营商状态查询 | ¥0.04 / 次 |
|| mobileDuration | 手机入网时长 | ¥0.10 / 次 |
|| mobile3Verify | 运营商三要素验证 | ¥0.15 / 次 |
|| realNameAuth | 实名认证 | ¥0.02 / 次 |
|| blackListV121_3 | 债务欺诈黑名单V3 | ¥0.22 / 次 |
|| loanRiskTagV10 | 风险变量V10 | ¥0.38 / 次 |
|| loanRiskTagV21 | 信用全景V21 | ¥0.30 / 次 |
|| loanRiskTagV11 | 借贷意向查询 | ¥0.20 / 次 |
|| loanRiskTagV12 | 特殊名单 | ¥0.00 / 次 |
|| mobileRiskV709 | 投诉风险筛查V709 | ¥0.20 / 次 |
|| blackListV110 | 特殊名单V110 | ¥0.20 / 次 |
|| personalLawsuit_cv1 | 个人法院涉诉定制版 | ¥0.20 / 次 |
| 产品编码 | 产品名称 | 价格 ¥2.06 | API接口名称 |
| :--- | :--- | :--- | :--- |
|| YYSYE7V5 | 手机在网状态V即时版 | ¥0.03 / 次 | /v1/mobile_status/check |
|| YYSYP0T4 | 手机号码在网时长V即时版 | ¥0.15 / 次 | /v2/mobile_online/check |
|| YYSYK9R4 | 全网手机三要素验证 | ¥0.16 / 次 | /communication/personal/1979 |
|| IVYZN2P8 | 公安二要素政务版 | ¥0.02 / 次 | /v4/id_card/check |
|| JRZQV3HM | 债务欺诈黑名单V3 | ¥0.22 / 次 | blackListV121_3 |
|| JRZQ4B6C | 探针C | ¥0.38 / 次 | loanRiskTagV10 |
|| JRZQ5E9F | 借选指数 | ¥0.30 / 次 | loanRiskTagV21 |
|| JRZQ3C7B | 借贷意向验证 | ¥0.20 / 次 | loanRiskTagV11 |
|| JRZQV7MD | 特殊名单 | ¥0.00 / 次 | loanRiskTagV12 |
|| JRZQVT43 | 投诉风险筛查V709投诉黑名单 | ¥0.20 / 次 | mobileRiskV709 |
|| JRZQV0MD | 行为黑名单 | ¥0.20 / 次 | blackListV110 |
|| FLXG7E8F | 个人司法数据查询 | ¥0.20 / 次 | personalLawsuit_cv1 |

View File

@@ -0,0 +1,117 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 读取JSON文件
// 读取JSON文件
const inputFile = path.join(__dirname, 'DWBG3BF9.json');
const data = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
// 姓名映射表(保持同一姓名脱敏后一致)
const nameMap = {
'何志勇': '何某某',
'覃圣有': '覃某',
'刘飞': '刘某某',
'陈波': '陈某某',
'覃小群': '覃某某',
'陈观海': '陈某某',
'刘国富': '刘某某'
};
// 脱敏函数
function desensitizeName(name) {
if (nameMap[name]) {
return nameMap[name];
}
// 对于未知的姓名,保留姓氏,名字用星号代替
if (name && name.length > 0) {
const surname = name[0];
return surname + '某某';
}
return name;
}
function desensitizeIdCard(idCard) {
if (!idCard || idCard.length !== 18) return idCard;
return idCard.substring(0, 6) + '********' + idCard.substring(14);
}
function desensitizeMobile(mobile) {
if (!mobile || mobile.length !== 11) return mobile;
return mobile.substring(0, 3) + '****' + mobile.substring(7);
}
function desensitizeText(text) {
if (!text || typeof text !== 'string') return text;
let result = text;
// 替换所有出现的人名
for (const [realName, maskedName] of Object.entries(nameMap)) {
// 替换姓名
const regex1 = new RegExp(realName, 'g');
result = result.replace(regex1, maskedName);
// 替换姓名+某的形式(如:何志某 -> 何某某某)
const regex2 = new RegExp(realName.substring(0, realName.length - 1) + '某', 'g');
result = result.replace(regex2, maskedName);
}
return result;
}
// 递归遍历对象进行脱敏
function desensitizeObject(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => desensitizeObject(item));
}
const result = {};
for (const [key, value] of Object.entries(obj)) {
switch (key) {
case 'name':
result[key] = desensitizeName(value);
break;
case 'id_card':
result[key] = desensitizeIdCard(value);
break;
case 'mobile':
result[key] = desensitizeMobile(value);
break;
case 'c_mc':
// 当事人姓名
result[key] = desensitizeName(value);
break;
case 'c_gkws_dsr':
case 'c_gkws_pjjg':
// 判决书内容中的文本
result[key] = desensitizeText(value);
break;
default:
result[key] = desensitizeObject(value);
break;
}
}
return result;
}
// 执行脱敏
const desensitizedData = desensitizeObject(data);
// 保存脱敏后的文件
const outputFile = path.join(__dirname, 'DWBG3BF9_desensitized.json');
fs.writeFileSync(outputFile, JSON.stringify(desensitizedData, null, 2), 'utf8');
console.log('脱敏完成!');
console.log('原始文件:', inputFile);
console.log('脱敏后文件:', outputFile);
// 显示脱敏摘要
console.log('\n脱敏摘要');
console.log('- 姓名:已脱敏(保留姓氏)');
console.log('- 身份证号已脱敏保留前6位和后4位');
console.log('- 手机号已脱敏保留前3位和后4位');
console.log('- 判决书文本中的姓名:已批量替换');

View File

@@ -0,0 +1,328 @@
package dwbg
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/shared/logger"
"go.uber.org/zap"
)
const dwbg3BF9FieldRA = "RA"
// dwbg3BF9OutputField 最终扁平结构中的单个顶层字段定义
type dwbg3BF9OutputField struct {
Key string // 顶层 JSON key
ApiCode string // 子产品编码RA 由聚合计算,无 ApiCode
Source string // 数据来源方法
}
// dwbg3BF9OutputSchema DWBG3BF9 最终返回扁平结构(顶层共 13 个 key
//
// {
// "RA": { ra_score, ra_level, ra_credit_score, ra_verify_score, ra_credit_risk_index, ra_performance_amt_index, ra_performance_cnt_index },
// "presence": { ... }, // YYSYE7V5
// "duration": { ... }, // YYSYP0T4
// "triple": { ... }, // YYSYK9R4
// "identity": { ... }, // IVYZN2P8
// "fraud": { ... }, // JRZQV3HM
// "probe": { ... }, // JRZQ4B6C
// "rating": { ... }, // JRZQ5E9F
// "intent": { ... }, // JRZQ3C7B
// "special": { ... }, // JRZQV7MD
// "complaint": { ... }, // JRZQVT43
// "behavior": { ... }, // JRZQV0MD
// "judicial": { ... }, // FLXG7E8F
// }
var dwbg3BF9OutputSchema = []dwbg3BF9OutputField{
{Key: dwbg3BF9FieldRA, Source: "buildDWBG3BF9RA"},
{Key: "presence", ApiCode: "YYSYE7V5", Source: "ProcessYYSYE7V5Request"},
{Key: "duration", ApiCode: "YYSYP0T4", Source: "ProcessYYSYP0T4Request"},
{Key: "triple", ApiCode: "YYSYK9R4", Source: "ProcessYYSYK9R4Request"},
{Key: "identity", ApiCode: "IVYZN2P8", Source: "ProcessIVYZN2P8Request"},
{Key: "fraud", ApiCode: "JRZQV3HM", Source: "ProcessJRZQV3HMRequest"},
{Key: "probe", ApiCode: "JRZQ4B6C", Source: "ProcessJRZQ4B6CRequest"},
{Key: "rating", ApiCode: "JRZQ5E9F", Source: "ProcessJRZQ5E9FRequest"},
{Key: "intent", ApiCode: "JRZQ3C7B", Source: "ProcessJRZQ3C7BRequest"},
{Key: "special", ApiCode: "JRZQV7MD", Source: "ProcessJRZQV7MDRequest"},
{Key: "complaint", ApiCode: "JRZQVT43", Source: "ProcessJRZQVT43Request"},
{Key: "behavior", ApiCode: "JRZQV0MD", Source: "ProcessJRZQV0MDRequest"},
{Key: "judicial", ApiCode: "FLXG7E8F", Source: "ProcessFLXG7E8FRequest"},
}
// dwbg3BF9OutputFieldOrder 最终 JSON 顶层字段顺序
var dwbg3BF9OutputFieldOrder = func() []string {
order := make([]string, len(dwbg3BF9OutputSchema))
for i, f := range dwbg3BF9OutputSchema {
order[i] = f.Key
}
return order
}()
// dwbg3BF9SubProductFieldNames 子产品编码 → 响应字段名
var dwbg3BF9SubProductFieldNames = func() map[string]string {
m := make(map[string]string, len(dwbg3BF9OutputSchema)-1)
for _, f := range dwbg3BF9OutputSchema {
if f.ApiCode == "" {
continue
}
m[f.ApiCode] = f.Key
}
return m
}()
// dwbg3BF9StripKeys 汇总报告中需剔除的上游流水号/追踪类字段
var dwbg3BF9StripKeys = map[string]struct{}{
"order_no": {},
"orderNo": {},
}
// stripDWBG3BF9MetaKeys 递归剔除子接口返回中的流水号等元数据字段
func stripDWBG3BF9MetaKeys(v interface{}) interface{} {
switch val := v.(type) {
case map[string]interface{}:
out := make(map[string]interface{}, len(val))
for k, item := range val {
if _, drop := dwbg3BF9StripKeys[k]; drop {
continue
}
out[k] = stripDWBG3BF9MetaKeys(item)
}
return out
case []interface{}:
out := make([]interface{}, len(val))
for i, item := range val {
out[i] = stripDWBG3BF9MetaKeys(item)
}
return out
default:
return v
}
}
// ProcessDWBG3BF9Request DWBG3BF9 API处理方法 - 个人风险档案
// 并发调用子产品处理器,将各子接口返回数据以大数据英文字段名拼接融合为一个对象
func ProcessDWBG3BF9Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.DWBG3BF9Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
log := logger.GetGlobalLogger()
log.Info("开始处理个人风险档案请求",
zap.String("name", paramsDto.Name),
zap.String("id_card", maskIDCard(paramsDto.IDCard)),
zap.String("mobile_no", maskMobile(paramsDto.MobileNo)),
)
subProducts := collectDWBG3BF9SubProducts(ctx, paramsDto, deps, log)
result := buildDWBG3BF9Response(subProducts)
respBytes, err := json.Marshal(result)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
log.Info("个人风险档案处理完成", zap.Int("field_count", len(result)))
return respBytes, nil
}
// buildDWBG3BF9Response 按 dwbg3BF9OutputSchema 构造最终扁平返回结构
func buildDWBG3BF9Response(subProducts map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{}, len(dwbg3BF9OutputSchema))
for _, field := range dwbg3BF9OutputSchema {
if field.Key == dwbg3BF9FieldRA {
result[field.Key] = buildDWBG3BF9RA(subProducts)
continue
}
if v, ok := subProducts[field.Key]; ok {
result[field.Key] = v
} else {
result[field.Key] = nil
}
}
return result
}
type dwbg3bf9APICall struct {
apiCode string
fieldName string
params map[string]interface{}
}
func collectDWBG3BF9SubProducts(
ctx context.Context,
params dto.DWBG3BF9Req,
deps *processors.ProcessorDependencies,
log *zap.Logger,
) map[string]interface{} {
apiCalls := []dwbg3bf9APICall{
{apiCode: "YYSYE7V5", fieldName: dwbg3BF9SubProductFieldNames["YYSYE7V5"], params: map[string]interface{}{"mobile_no": params.MobileNo}},
{apiCode: "YYSYP0T4", fieldName: dwbg3BF9SubProductFieldNames["YYSYP0T4"], params: map[string]interface{}{"mobile_no": params.MobileNo}},
{
apiCode: "YYSYK9R4",
fieldName: dwbg3BF9SubProductFieldNames["YYSYK9R4"],
params: map[string]interface{}{
"mobile_no": params.MobileNo,
"id_card": params.IDCard,
"name": params.Name,
},
},
{
apiCode: "IVYZN2P8",
fieldName: dwbg3BF9SubProductFieldNames["IVYZN2P8"],
params: map[string]interface{}{
"id_card": params.IDCard,
"name": params.Name,
},
},
{
apiCode: "JRZQV3HM",
fieldName: dwbg3BF9SubProductFieldNames["JRZQV3HM"],
params: map[string]interface{}{
"id_card": params.IDCard,
"name": params.Name,
"mobile_no": params.MobileNo,
},
},
{
apiCode: "JRZQ4B6C",
fieldName: dwbg3BF9SubProductFieldNames["JRZQ4B6C"],
params: map[string]interface{}{
"mobile_no": params.MobileNo,
"id_card": params.IDCard,
"name": params.Name,
"authorized": "1",
},
},
{
apiCode: "JRZQ5E9F",
fieldName: dwbg3BF9SubProductFieldNames["JRZQ5E9F"],
params: map[string]interface{}{
"mobile_no": params.MobileNo,
"id_card": params.IDCard,
"name": params.Name,
"authorized": "1",
},
},
{
apiCode: "JRZQ3C7B",
fieldName: dwbg3BF9SubProductFieldNames["JRZQ3C7B"],
params: map[string]interface{}{
"mobile_no": params.MobileNo,
"id_card": params.IDCard,
"name": params.Name,
"authorized": "1",
},
},
{
apiCode: "JRZQV7MD",
fieldName: dwbg3BF9SubProductFieldNames["JRZQV7MD"],
params: map[string]interface{}{
"id_card": params.IDCard,
"name": params.Name,
"mobile_no": params.MobileNo,
},
},
{
apiCode: "JRZQVT43",
fieldName: dwbg3BF9SubProductFieldNames["JRZQVT43"],
params: map[string]interface{}{
"id_card": params.IDCard,
"name": params.Name,
"mobile_no": params.MobileNo,
},
},
{
apiCode: "JRZQV0MD",
fieldName: dwbg3BF9SubProductFieldNames["JRZQV0MD"],
params: map[string]interface{}{
"id_card": params.IDCard,
"name": params.Name,
"mobile_no": params.MobileNo,
},
},
{
apiCode: "FLXG7E8F",
fieldName: dwbg3BF9SubProductFieldNames["FLXG7E8F"],
params: map[string]interface{}{
"name": params.Name,
"id_card": params.IDCard,
"mobile_no": params.MobileNo,
},
},
}
type callResult struct {
fieldName string
data interface{}
err error
}
results := make(chan callResult, len(apiCalls))
var wg sync.WaitGroup
for _, apiCall := range apiCalls {
wg.Add(1)
go func(ac dwbg3bf9APICall) {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
log.Error("调用子产品处理器时发生panic",
zap.String("api_code", ac.apiCode),
zap.String("field_name", ac.fieldName),
zap.Any("panic", r),
)
results <- callResult{ac.fieldName, nil, fmt.Errorf("处理器panic: %v", r)}
}
}()
paramsBytes, err := json.Marshal(ac.params)
if err != nil {
results <- callResult{ac.fieldName, nil, err}
return
}
data, err := callProcessor(ctx, ac.apiCode, paramsBytes, deps)
results <- callResult{ac.fieldName, data, err}
}(apiCall)
}
go func() {
wg.Wait()
close(results)
}()
output := make(map[string]interface{}, len(apiCalls))
successCount := 0
for result := range results {
if result.err != nil {
log.Warn("子产品调用失败,该字段置空",
zap.String("field_name", result.fieldName),
zap.Error(result.err),
)
output[result.fieldName] = nil
continue
}
output[result.fieldName] = stripDWBG3BF9MetaKeys(result.data)
successCount++
}
log.Info("子产品调用完成",
zap.Int("total", len(apiCalls)),
zap.Int("success", successCount),
zap.Int("failed", len(apiCalls)-successCount),
)
return output
}

View File

@@ -0,0 +1,486 @@
package dwbg
import (
"math"
"strconv"
"strings"
)
const raScoreMax = 1000
const (
raWeightVerify = 0.10
raWeightJudicialBase = 0.50
raWeightCreditBase = 0.40
raHighRiskThreshold = 400
raForcedFScoreCap = 500 // 双高风险强制 F 档时ra_score 上限(保留加权分,不置 0
raForcedFTotalHitThreshold = 5 // 司法 + 借贷命中总条数须超过此值才触发强制 F
raFraudPointsPerHit = 23 // 欺诈每命中一项扣分(非整齐倍数)
// 司法涉诉扣分系数(件数/条数 × 系数,带非整齐上限)
raJudicialLawsuitTotalPerCase = 47
raJudicialLawsuitTotalCap = 387
raJudicialLawsuitWeiPerCase = 38
raJudicialLawsuitWeiCap = 293
raJudicialLawsuitBeigaoPerCase = 33
raJudicialLawsuitBeigaoCap = 247
raJudicialBreachPerCase = 143
raJudicialBreachCap = 437
raJudicialConsumptionPerCase = 187
raJudicialConsumptionCap = 413
// 借贷维度扣分
raCreditOverduePoints = 287
raCreditSleepPoints = 143
raCreditPerformancePoints = 97
raCreditProbeHitPoints = 103
raCreditIntentReject = 387
raCreditIntentReview = 237
raCreditIntentWeightMult = 4 // Rule_final_weight × 4
raCreditIntentWeightCap = 243
// 身份核验扣分
raVerifyMismatchPoints = 387
raVerifyPresenceDesc = 73
raVerifyPresenceStatus = 47
xypModelScoreMin = 350
xypModelScoreRiskThreshold = 650 // [350,950] 区间中位,低于此值视为高风险
xypModelScoreRiskCapPerField = 97 // 单项上限,三项合计最高 291
)
// buildDWBG3BF9RA 构建顶层 RA 总体安全评估(千分制,分值越高越安全)
//
// 输出字段(对外):
// - ra_score / ra_level → 综合安全分与等级
// - ra_credit_score → 借贷维度安全分(参与加权)
// - ra_verify_score → 个人身份区安全分(参与加权)
// - ra_credit_risk_index → 信用风险指数(越高风险越大,与 ra_score 同向)
// - ra_performance_amt_index → 履约金额综合指数(越高越好)
// - ra_performance_cnt_index → 履约笔数综合指数(越高越好)
// 内部仍计算 fraud/judicial 分用于 ra_score 加权,但不对外返回。
func buildDWBG3BF9RA(data map[string]interface{}) map[string]interface{} {
creditScore := calcRACreditScore(data)
judicialScore := calcRAJudicialScore(data)
verifyScore := calcRAVerifyScore(data)
total := calcRAWightedScore(verifyScore, judicialScore, creditScore)
level := raLevelFromScore(total)
if isRAForcedFGrade(data, judicialScore, creditScore) {
level = "F"
total = clampRAInt(total, 0, raForcedFScoreCap)
}
return map[string]interface{}{
"ra_score": total,
"ra_level": level,
"ra_credit_score": creditScore,
"ra_verify_score": verifyScore,
"ra_credit_risk_index": calcRACreditRiskIndex(data, creditScore),
"ra_performance_amt_index": calcRAPerformanceAmtIndex(data),
"ra_performance_cnt_index": calcRAPerformanceCntIndex(data),
}
}
// calcRAWightedScore 三维度动态加权汇总(身份 10% 固定 + 司法/借贷动态 90%
func calcRAWightedScore(verifyScore, judicialScore, creditScore int) int {
wJudicial, wCredit := calcRADynamicWeights(judicialScore, creditScore)
total := int(math.Round(
float64(verifyScore)*raWeightVerify +
float64(judicialScore)*wJudicial +
float64(creditScore)*wCredit,
))
return clampRAInt(total, 0, raScoreMax)
}
// calcRADynamicWeights 按司法/借贷风险场景分配剩余 90% 权重(身份固定 10%
func calcRADynamicWeights(judicialScore, creditScore int) (wJudicial, wCredit float64) {
hasJudicialRisk := judicialScore < raScoreMax
hasCreditRisk := creditScore < raScoreMax
switch {
case hasJudicialRisk && hasCreditRisk:
return 0.70, 0.20
case hasJudicialRisk:
return 0.65, 0.25
case hasCreditRisk:
return 0.40, 0.50
default:
return raWeightJudicialBase, raWeightCreditBase
}
}
// isRAForcedFGrade 司法 + 借贷双重高风险且命中总条数 > 5 时强制 F 档
func isRAForcedFGrade(data map[string]interface{}, judicialScore, creditScore int) bool {
if judicialScore > raHighRiskThreshold || creditScore > raHighRiskThreshold {
return false
}
totalHits := calcRAJudicialHitCount(data) + calcRACreditHitCount(data)
return totalHits > raForcedFTotalHitThreshold
}
// calcRAJudicialHitCount 统计司法维度命中条数(涉诉件数 + 失信条数 + 限高条数)
func calcRAJudicialHitCount(data map[string]interface{}) int {
judicial := raAsMap(data["judicial"])
if judicial == nil {
return 0
}
judicialData := raAsMap(judicial["judicial_data"])
if judicialData == nil {
return 0
}
hits := 0
if lawsuitStat := raAsMap(judicialData["lawsuitStat"]); lawsuitStat != nil {
for _, section := range lawsuitStat {
sectionMap := raAsMap(section)
if sectionMap == nil {
continue
}
count := raAsMap(sectionMap["count"])
if count == nil {
continue
}
hits += raAsInt(count["count_total"])
}
}
hits += len(raAsSlice(judicialData["breachCaseList"]))
hits += len(raAsSlice(judicialData["consumptionRestrictionList"]))
return hits
}
// calcRACreditHitCount 统计借贷维度命中条数(探针 / 意向 / 借选模型分各算 1 条)
func calcRACreditHitCount(data map[string]interface{}) int {
hits := 0
if probe := raAsMap(data["probe"]); probe != nil {
if raAsString(probe["currently_overdue"]) == "1" {
hits++
}
if raAsString(probe["acc_sleep"]) == "1" {
hits++
}
if raAsString(probe["currently_performance"]) == "0" {
hits++
}
if raAsString(probe["result_code"]) == "1" {
hits++
}
}
if intent := raAsMap(data["intent"]); intent != nil {
switch raAsString(intent["Rule_final_decision"]) {
case "Reject", "Review":
hits++
}
}
if rating := raAsMap(data["rating"]); rating != nil {
for _, key := range []string{"xyp_model_score_high", "xyp_model_score_mid", "xyp_model_score_low"} {
score := raXypModelScore(rating[key])
if score >= xypModelScoreMin && score < xypModelScoreRiskThreshold {
hits++
}
}
}
return hits
}
// raLevelFromScore 由 ra_score 映射等级(千分制,越高越安全,每档 100 分)
// A: 900-1000 B: 800-899 C: 700-799 D: 600-699 E: 500-599 F: 0-499
func raLevelFromScore(score int) string {
switch {
case score >= 900:
return "A"
case score >= 800:
return "B"
case score >= 700:
return "C"
case score >= 600:
return "D"
case score >= 500:
return "E"
default:
return "F"
}
}
// toRASafetyScore 将风险扣分转为安全分:安全分 = 1000 - 风险扣分
func toRASafetyScore(riskPoints int) int {
return clampRAInt(raScoreMax-riskPoints, 0, raScoreMax)
}
// calcRAFraudScore 欺诈/黑名单子维度安全分来源calcRAFraudRiskPoints
// 统计方式:满分 1000根据 behavior/complaint/fraud/special 风险信号扣分后取补集;同时计入 ra_verify_score
func calcRAFraudScore(data map[string]interface{}) int {
return toRASafetyScore(calcRAFraudRiskPoints(data))
}
// calcRAFraudRiskPoints 统计欺诈维度风险扣分(计入个人身份区,分值越高代表越不安全)
// 每命中一项扣 raFraudPointsPerHit23多项独立累加
func calcRAFraudRiskPoints(data map[string]interface{}) int {
hits := 0
// 来源子字段 behaviorJRZQV0MD 行为黑名单)
if behavior := raAsMap(data["behavior"]); behavior != nil {
if result := raAsMap(behavior["result"]); result != nil {
if raAsString(result["black_list"]) == "1" {
hits++
}
for k, v := range result {
if strings.HasPrefix(k, "black_tag") && raAsString(v) == "1" {
hits++
}
}
}
}
// 来源子字段 complaintJRZQVT43 投诉风险筛查)
if complaint := raAsMap(data["complaint"]); complaint != nil {
if result := raAsMap(complaint["result"]); result != nil {
if raAsInt(result["score"]) > 0 {
hits++
}
}
}
// 来源子字段 fraudJRZQV3HM 债务欺诈黑名单)
if fraud := raAsMap(data["fraud"]); fraud != nil {
if raAsInt(fraud["hit"]) == 1 || raAsString(fraud["hit"]) == "1" {
hits++
}
}
// 来源子字段 specialJRZQV7MD 特殊名单)
if special := raAsMap(data["special"]); special != nil && len(special) > 0 {
switch raAsString(special["Rule_final_decision"]) {
case "Reject", "Review":
hits++
}
}
return clampRAInt(hits*raFraudPointsPerHit, 0, raScoreMax)
}
// calcRACreditScore 借贷/逾期维度安全分来源calcRACreditRiskPoints
// 统计方式:满分 1000根据 probe/intent/rating 风险信号扣分后取补集
func calcRACreditScore(data map[string]interface{}) int {
return toRASafetyScore(calcRACreditRiskPoints(data))
}
// calcRACreditRiskPoints 统计借贷维度风险扣分
func calcRACreditRiskPoints(data map[string]interface{}) int {
risk := 0
// 来源子字段 probeJRZQ4B6C 探针C
if probe := raAsMap(data["probe"]); probe != nil {
if raAsString(probe["currently_overdue"]) == "1" {
risk += raCreditOverduePoints
}
if raAsString(probe["acc_sleep"]) == "1" {
risk += raCreditSleepPoints
}
if raAsString(probe["currently_performance"]) == "0" {
risk += raCreditPerformancePoints
}
if raAsString(probe["result_code"]) == "1" {
risk += raCreditProbeHitPoints
}
}
// 来源子字段 intentJRZQ3C7B 借贷意向验证)
if intent := raAsMap(data["intent"]); intent != nil {
switch raAsString(intent["Rule_final_decision"]) {
case "Reject":
risk += raCreditIntentReject
case "Review":
risk += raCreditIntentReview
}
weight := raAsInt(intent["Rule_final_weight"])
if weight > 0 {
risk += clampRAInt(weight*raCreditIntentWeightMult, 0, raCreditIntentWeightCap)
}
}
// 来源子字段 ratingJRZQ5E9F / loanRiskTagV21 借选指数)
if rating := raAsMap(data["rating"]); rating != nil {
risk += calcRARatingXypModelRiskPoints(rating)
}
return clampRAInt(risk, 0, raScoreMax)
}
// calcRARatingXypModelRiskPoints 根据借选指数三个模型分统计风险扣分
// xyp_model_score_* 范围 [350,950],越大逾期率越低;-1 为未命中,不参与计分
func calcRARatingXypModelRiskPoints(rating map[string]interface{}) int {
risk := 0
for _, key := range []string{"xyp_model_score_high", "xyp_model_score_mid", "xyp_model_score_low"} {
score := raXypModelScore(rating[key])
if score < xypModelScoreMin || score >= xypModelScoreRiskThreshold {
continue
}
risk += clampRAInt(xypModelScoreRiskThreshold-score, 0, xypModelScoreRiskCapPerField)
}
return risk
}
// raXypModelScore 解析借选指数模型分,未命中(-1 / 空)返回 -1
func raXypModelScore(v interface{}) int {
s := raAsString(v)
if s == "" || s == "-1" {
return -1
}
return raAsInt(v)
}
// calcRAJudicialScore 司法涉诉维度安全分来源calcRAJudicialRiskPoints
// 统计方式:满分 1000根据 judicial.judicial_data 涉诉统计扣分后取补集
func calcRAJudicialScore(data map[string]interface{}) int {
return toRASafetyScore(calcRAJudicialRiskPoints(data))
}
// calcRAJudicialRiskPoints 统计司法维度风险扣分
func calcRAJudicialRiskPoints(data map[string]interface{}) int {
risk := 0
// 来源子字段 judicialFLXG7E8F 个人司法数据查询)
judicial := raAsMap(data["judicial"])
if judicial == nil {
return 0
}
judicialData := raAsMap(judicial["judicial_data"])
if judicialData == nil {
return 0
}
// lawsuitStat 下 civil/criminal/administrative/preservation 等节点累加
if lawsuitStat := raAsMap(judicialData["lawsuitStat"]); lawsuitStat != nil {
for _, section := range lawsuitStat {
sectionMap := raAsMap(section)
if sectionMap == nil {
continue
}
count := raAsMap(sectionMap["count"])
if count == nil {
continue
}
risk += clampRAInt(raAsInt(count["count_total"])*raJudicialLawsuitTotalPerCase, 0, raJudicialLawsuitTotalCap)
risk += clampRAInt(raAsInt(count["count_wei_total"])*raJudicialLawsuitWeiPerCase, 0, raJudicialLawsuitWeiCap)
risk += clampRAInt(raAsInt(count["count_beigao"])*raJudicialLawsuitBeigaoPerCase, 0, raJudicialLawsuitBeigaoCap)
}
}
risk += clampRAInt(len(raAsSlice(judicialData["breachCaseList"]))*raJudicialBreachPerCase, 0, raJudicialBreachCap)
risk += clampRAInt(len(raAsSlice(judicialData["consumptionRestrictionList"]))*raJudicialConsumptionPerCase, 0, raJudicialConsumptionCap)
return clampRAInt(risk, 0, raScoreMax)
}
// calcRAVerifyScore 个人身份区安全分来源calcRAVerifyRiskPoints
// 统计方式:满分 1000核验异常 + 欺诈/黑名单信号扣分后取补集,参与 ra_score 10% 加权
func calcRAVerifyScore(data map[string]interface{}) int {
return toRASafetyScore(calcRAVerifyRiskPoints(data))
}
// calcRAVerifyRiskPoints 统计个人身份区风险扣分(核验 + 欺诈/黑名单)
func calcRAVerifyRiskPoints(data map[string]interface{}) int {
risk := 0
// 来源子字段 tripleYYSYK9R4 三要素验证)
if triple := raAsMap(data["triple"]); triple != nil {
if state := raAsString(triple["state"]); state != "" && state != "1" {
risk += raVerifyMismatchPoints
}
}
// 来源子字段 identityIVYZN2P8 二要素认证)
if identity := raAsMap(data["identity"]); identity != nil {
if result := raAsInt(identity["result"]); result != 0 {
risk += raVerifyMismatchPoints
}
}
// 来源子字段 presenceYYSYE7V5 在网状态)
if presence := raAsMap(data["presence"]); presence != nil {
desc := raAsString(presence["desc"])
if strings.Contains(desc, "停机") || strings.Contains(desc, "销号") || strings.Contains(desc, "不在网") {
risk += raVerifyPresenceDesc
}
if status := raAsInt(presence["status"]); status > 1 {
risk += raVerifyPresenceStatus
}
}
// 来源子字段 behavior / complaint / fraud / special欺诈/黑名单,与 calcRAFraudRiskPoints 一致)
risk += calcRAFraudRiskPoints(data)
return clampRAInt(risk, 0, raScoreMax)
}
func raAsMap(v interface{}) map[string]interface{} {
m, ok := v.(map[string]interface{})
if !ok || m == nil {
return nil
}
return m
}
func raAsSlice(v interface{}) []interface{} {
s, ok := v.([]interface{})
if !ok {
return nil
}
return s
}
func raAsString(v interface{}) string {
switch val := v.(type) {
case string:
return strings.TrimSpace(val)
case float64:
return strconv.FormatInt(int64(val), 10)
case int:
return strconv.Itoa(val)
case int64:
return strconv.FormatInt(val, 10)
case bool:
if val {
return "1"
}
return "0"
default:
return ""
}
}
func raAsInt(v interface{}) int {
switch val := v.(type) {
case int:
return val
case int64:
return int(val)
case float64:
return int(val)
case string:
n, err := strconv.Atoi(strings.TrimSpace(val))
if err == nil {
return n
}
case bool:
if val {
return 1
}
}
return 0
}
func clampRAInt(v, min, max int) int {
if v < min {
return min
}
if v > max {
return max
}
return v
}

View File

@@ -0,0 +1,234 @@
package dwbg
import (
"math"
"regexp"
"strconv"
)
// raIndexField 借选指数区间桶加权项
type raIndexField struct {
key string
weight float64
failure bool // true 表示桶值越高表现越差(如失败次数、距上次成功天数)
}
// 履约金额:成功金额 + 历史还款总额,扣减失败金额
var raPerformanceAmtFields = []raIndexField{
{key: "xyp_t01aazzzc", weight: 0.30},
{key: "xyp_cpl0035", weight: 0.12},
{key: "xyp_cpl0037", weight: 0.14},
{key: "xyp_cpl0039", weight: 0.14},
{key: "xyp_cpl0041", weight: 0.12},
{key: "xyp_cpl0034", weight: 0.09, failure: true},
{key: "xyp_cpl0036", weight: 0.09, failure: true},
}
// 履约笔数:成功笔数,扣减失败笔数及距上次成功天数
var raPerformanceCntFields = []raIndexField{
{key: "xyp_cpl0019", weight: 0.12},
{key: "xyp_cpl0021", weight: 0.14},
{key: "xyp_cpl0023", weight: 0.14},
{key: "xyp_cpl0025", weight: 0.12},
{key: "xyp_cpl0027", weight: 0.10},
{key: "xyp_cpl0068", weight: 0.18, failure: true},
{key: "xyp_cpl0018", weight: 0.06, failure: true},
{key: "xyp_cpl0020", weight: 0.07, failure: true},
{key: "xyp_cpl0022", weight: 0.07, failure: true},
}
var raProbeOverdueAmtPattern = regexp.MustCompile(`(\d+)`)
// calcRACreditRiskIndex 信用风险指数0-1000越高信用风险越大与 ra_score 方向一致)
func calcRACreditRiskIndex(data map[string]interface{}, creditSafetyScore int) int {
riskFromModel := raScoreMax - creditSafetyScore
rating := raAsMap(data["rating"])
if rating == nil {
return riskFromModel
}
raw := raAsString(rating["xyp_cpl0081"])
if raw == "" || raw == "0" || raw == "-1" {
return riskFromModel
}
v, ok := raParseFloat(raw)
if !ok || v < 0 || v > 1 {
return riskFromModel
}
riskFromRating := int(math.Round(v * float64(raScoreMax)))
if riskFromRating > riskFromModel {
return riskFromRating
}
return riskFromModel
}
// calcRAPerformanceAmtIndex 履约金额综合指数0-1000越高履约金额表现越好
func calcRAPerformanceAmtIndex(data map[string]interface{}) int {
rating := raAsMap(data["rating"])
probe := raAsMap(data["probe"])
if rating != nil {
if idx, ok := calcRACompositeBucketIndex(rating, raPerformanceAmtFields); ok {
if probe != nil {
probeIdx := calcRAPerformanceAmtIndexFromProbe(probe)
return clampRAInt(int(math.Round(float64(idx)*0.75+float64(probeIdx)*0.25)), 0, raScoreMax)
}
return idx
}
}
if probe != nil {
return calcRAPerformanceAmtIndexFromProbe(probe)
}
return 500
}
// calcRAPerformanceCntIndex 履约笔数综合指数0-1000越高履约笔数表现越好
func calcRAPerformanceCntIndex(data map[string]interface{}) int {
rating := raAsMap(data["rating"])
probe := raAsMap(data["probe"])
if rating != nil {
if idx, ok := calcRACompositeBucketIndex(rating, raPerformanceCntFields); ok {
if probe != nil {
probeIdx := calcRAPerformanceCntIndexFromProbe(probe)
return clampRAInt(int(math.Round(float64(idx)*0.75+float64(probeIdx)*0.25)), 0, raScoreMax)
}
return idx
}
}
if probe != nil {
return calcRAPerformanceCntIndexFromProbe(probe)
}
return 500
}
func calcRACompositeBucketIndex(rating map[string]interface{}, fields []raIndexField) (int, bool) {
totalWeight := 0.0
totalScore := 0.0
for _, f := range fields {
bucket := raAsInt(rating[f.key])
if bucket <= 0 {
continue
}
tierScore := raBucketTierScore(bucket)
if f.failure {
tierScore = raScoreMax - tierScore
}
totalScore += float64(tierScore) * f.weight
totalWeight += f.weight
}
if totalWeight == 0 {
return 0, false
}
return clampRAInt(int(math.Round(totalScore/totalWeight)), 0, raScoreMax), true
}
func raBucketTierScore(bucket int) int {
switch {
case bucket <= 0:
return 0
case bucket == 1:
return 280
case bucket == 2:
return 530
case bucket == 3:
return 760
default:
return 920
}
}
func calcRAPerformanceAmtIndexFromProbe(probe map[string]interface{}) int {
score := 620
switch raAsString(probe["currently_performance"]) {
case "1":
score += 180
case "0":
score -= 280
}
if raAsString(probe["currently_overdue"]) == "1" {
score -= raProbeOverdueAmtPenalty(probe["max_overdue_amt"])
}
if raAsString(probe["acc_sleep"]) == "1" {
score -= 80
}
if raAsString(probe["acc_exc"]) == "1" {
score -= 60
}
return clampRAInt(score, 0, raScoreMax)
}
func calcRAPerformanceCntIndexFromProbe(probe map[string]interface{}) int {
score := 580
switch raAsString(probe["currently_performance"]) {
case "1":
score += 220
case "0":
score -= 260
}
if raAsString(probe["currently_overdue"]) == "1" {
score -= 150
}
if raAsString(probe["result_code"]) == "1" {
score -= 120
}
if raAsString(probe["acc_sleep"]) == "1" {
score -= 90
}
if raAsString(probe["acc_exc"]) == "1" {
score -= 70
}
return clampRAInt(score, 0, raScoreMax)
}
func raProbeOverdueAmtPenalty(v interface{}) int {
mid := raProbeAmtMidpoint(v)
switch {
case mid >= 10000:
return 280
case mid >= 4000:
return 200
case mid >= 1000:
return 120
case mid > 0:
return 60
default:
return 100
}
}
func raProbeAmtMidpoint(v interface{}) int {
s := raAsString(v)
if s == "" {
return 0
}
matches := raProbeOverdueAmtPattern.FindAllString(s, -1)
if len(matches) == 0 {
return 0
}
if len(matches) == 1 {
return raAsInt(matches[0])
}
low := raAsInt(matches[0])
high := raAsInt(matches[len(matches)-1])
if high <= 0 {
return low
}
return (low + high) / 2
}
func raParseFloat(raw string) (float64, bool) {
f, err := strconv.ParseFloat(raw, 64)
if err != nil || math.IsNaN(f) || math.IsInf(f, 0) {
return 0, false
}
return f, true
}

View File

@@ -0,0 +1,386 @@
package dwbg
import (
"encoding/json"
"math"
"os"
"path/filepath"
"testing"
)
func TestBuildDWBG3BF9RAFromSample(t *testing.T) {
raw, err := os.ReadFile(filepath.Join("DWBG3BF9.json"))
if err != nil {
t.Skip("DWBG3BF9.json not found")
}
var data map[string]interface{}
if err := json.Unmarshal(raw, &data); err != nil {
t.Fatalf("unmarshal sample: %v", err)
}
ra := buildDWBG3BF9RA(data)
required := []string{
"ra_score",
"ra_level",
"ra_credit_score",
"ra_verify_score",
"ra_credit_risk_index",
"ra_performance_amt_index",
"ra_performance_cnt_index",
}
for _, key := range required {
if _, ok := ra[key]; !ok {
t.Fatalf("RA missing key %s", key)
}
}
hidden := []string{"ra_fraud_score", "ra_judicial_score"}
for _, key := range hidden {
if _, ok := ra[key]; ok {
t.Fatalf("RA should not expose %s", key)
}
}
score := raAsInt(ra["ra_score"])
if score < 0 || score > raScoreMax {
t.Fatalf("ra_score out of range: %d", score)
}
level := raAsString(ra["ra_level"])
validLevels := map[string]bool{"A": true, "B": true, "C": true, "D": true, "E": true, "F": true}
if !validLevels[level] {
t.Fatalf("invalid ra_level: %s", level)
}
if raAsString(ra["ra_level"]) != "D" {
t.Fatalf("sample ra_level want D, got %s", raAsString(ra["ra_level"]))
}
if raAsInt(ra["ra_credit_risk_index"]) != 1000 {
t.Fatalf("sample credit risk index want 1000, got %d", raAsInt(ra["ra_credit_risk_index"]))
}
if raAsInt(ra["ra_performance_amt_index"]) != 60 {
t.Fatalf("sample perf amt index want 60, got %d", raAsInt(ra["ra_performance_amt_index"]))
}
if raAsInt(ra["ra_performance_cnt_index"]) != 0 {
t.Fatalf("sample perf cnt index want 0, got %d", raAsInt(ra["ra_performance_cnt_index"]))
}
t.Logf("RA sample: score=%d level=%s credit=%d verify=%d creditRisk=%d perfAmt=%d perfCnt=%d",
raAsInt(ra["ra_score"]),
level,
raAsInt(ra["ra_credit_score"]),
raAsInt(ra["ra_verify_score"]),
raAsInt(ra["ra_credit_risk_index"]),
raAsInt(ra["ra_performance_amt_index"]),
raAsInt(ra["ra_performance_cnt_index"]),
)
}
func TestRALevelFromScore(t *testing.T) {
cases := map[int]string{
0: "F",
499: "F",
500: "E",
599: "E",
600: "D",
699: "D",
700: "C",
799: "C",
800: "B",
899: "B",
900: "A",
1000: "A",
}
for score, want := range cases {
if got := raLevelFromScore(score); got != want {
t.Fatalf("score %d: got %s want %s", score, got, want)
}
}
}
func TestSafetyScoreHigherIsSafer(t *testing.T) {
clean := map[string]interface{}{
"behavior": map[string]interface{}{"result": map[string]interface{}{"black_list": "0"}},
"complaint": nil,
"fraud": nil,
"special": nil,
"probe": nil,
"intent": nil,
"rating": nil,
"judicial": nil,
"triple": map[string]interface{}{"state": "1"},
"identity": map[string]interface{}{"result": 0},
"presence": map[string]interface{}{"desc": "正常", "status": 1},
}
ra := buildDWBG3BF9RA(clean)
if raAsInt(ra["ra_score"]) < 800 {
t.Fatalf("clean data should have safety score >= 800, got %d", raAsInt(ra["ra_score"]))
}
if raLevelFromScore(raAsInt(ra["ra_score"])) != "A" {
t.Fatalf("clean data should be level A, got %s", raLevelFromScore(raAsInt(ra["ra_score"])))
}
}
func TestCalcRADynamicWeights(t *testing.T) {
cases := []struct {
judicial, credit int
wantJudicial float64
wantCredit float64
}{
{1000, 1000, 0.50, 0.40},
{900, 1000, 0.65, 0.25},
{1000, 900, 0.40, 0.50},
{900, 900, 0.70, 0.20},
}
for _, tc := range cases {
gotJ, gotC := calcRADynamicWeights(tc.judicial, tc.credit)
if gotJ != tc.wantJudicial || gotC != tc.wantCredit {
t.Fatalf("judicial=%d credit=%d: got (%.2f, %.2f) want (%.2f, %.2f)",
tc.judicial, tc.credit, gotJ, gotC, tc.wantJudicial, tc.wantCredit)
}
}
}
func TestRAForcedFGrade(t *testing.T) {
data := map[string]interface{}{
"behavior": nil,
"complaint": nil,
"fraud": nil,
"special": nil,
"probe": map[string]interface{}{
"currently_overdue": "1",
"currently_performance": "0",
"result_code": "1",
},
"intent": map[string]interface{}{
"Rule_final_decision": "Reject",
"Rule_final_weight": "40",
},
"rating": nil,
"triple": map[string]interface{}{"state": "1"},
"identity": map[string]interface{}{"result": 0},
"presence": map[string]interface{}{"desc": "正常", "status": 1},
"judicial": map[string]interface{}{
"judicial_data": map[string]interface{}{
"breachCaseList": []interface{}{
map[string]interface{}{"caseNo": "1"},
map[string]interface{}{"caseNo": "2"},
map[string]interface{}{"caseNo": "3"},
},
"consumptionRestrictionList": []interface{}{
map[string]interface{}{"caseNo": "4"},
map[string]interface{}{"caseNo": "5"},
},
"lawsuitStat": map[string]interface{}{},
},
},
}
ra := buildDWBG3BF9RA(data)
// 司法 197 + 借贷 0双风险权重 70%/20%,身份 1000 → 加权分 238强制 F 档保留分数
if raAsInt(ra["ra_score"]) != 238 {
t.Fatalf("forced F should keep weighted ra_score=238, got %d", raAsInt(ra["ra_score"]))
}
if raAsString(ra["ra_level"]) != "F" {
t.Fatalf("forced F should have ra_level=F, got %s", raAsString(ra["ra_level"]))
}
}
func TestIsRAForcedFGradeRequiresMoreThan5Hits(t *testing.T) {
fiveHits := map[string]interface{}{
"judicial": map[string]interface{}{
"judicial_data": map[string]interface{}{
"breachCaseList": []interface{}{
map[string]interface{}{"caseNo": "1"},
map[string]interface{}{"caseNo": "2"},
map[string]interface{}{"caseNo": "3"},
},
"consumptionRestrictionList": []interface{}{
map[string]interface{}{"caseNo": "4"},
map[string]interface{}{"caseNo": "5"},
},
},
},
}
if calcRAJudicialHitCount(fiveHits)+calcRACreditHitCount(fiveHits) != 5 {
t.Fatal("test setup want 5 total hits")
}
if isRAForcedFGrade(fiveHits, 150, 100) {
t.Fatal("total hits = 5 should not trigger forced F")
}
sixHits := map[string]interface{}{
"probe": map[string]interface{}{"currently_overdue": "1"},
"judicial": map[string]interface{}{
"judicial_data": map[string]interface{}{
"breachCaseList": []interface{}{
map[string]interface{}{"caseNo": "1"},
map[string]interface{}{"caseNo": "2"},
map[string]interface{}{"caseNo": "3"},
},
"consumptionRestrictionList": []interface{}{
map[string]interface{}{"caseNo": "4"},
map[string]interface{}{"caseNo": "5"},
},
},
},
}
if calcRAJudicialHitCount(sixHits)+calcRACreditHitCount(sixHits) != 6 {
t.Fatal("test setup want 6 total hits")
}
if !isRAForcedFGrade(sixHits, 150, 100) {
t.Fatal("total hits = 6 with dual high should trigger forced F")
}
}
func TestRAForcedFGradeScoreCap(t *testing.T) {
// 加权分高于 500 时,强制 F 档封顶 500
verify, judicial, credit := 1000, 1000, 1000
raw := calcRAWightedScore(verify, judicial, credit)
if raw != 1000 {
t.Fatalf("baseline weighted score want 1000, got %d", raw)
}
// 模拟双高:司法/借贷维度分均 ≤ 400但加权后可能 > 500此处用边界值构造
verify, judicial, credit = 1000, 400, 400
raw = calcRAWightedScore(verify, judicial, credit)
// 1000×0.10 + 400×0.70 + 400×0.20 = 100+280+80 = 460
if raw != 460 {
t.Fatalf("dual-risk weighted score want 460, got %d", raw)
}
data := map[string]interface{}{
"behavior": nil, "complaint": nil, "fraud": nil, "special": nil,
"probe": map[string]interface{}{"currently_overdue": "1"},
"intent": map[string]interface{}{"Rule_final_decision": "Reject"},
"rating": nil,
"triple": map[string]interface{}{"state": "1"},
"identity": map[string]interface{}{"result": 0},
"presence": map[string]interface{}{"desc": "正常", "status": 1},
"judicial": map[string]interface{}{
"judicial_data": map[string]interface{}{
"breachCaseList": []interface{}{
map[string]interface{}{"caseNo": "1"},
map[string]interface{}{"caseNo": "2"},
map[string]interface{}{"caseNo": "3"},
},
"consumptionRestrictionList": []interface{}{
map[string]interface{}{"caseNo": "4"},
map[string]interface{}{"caseNo": "5"},
},
},
},
}
ra := buildDWBG3BF9RA(data)
score := raAsInt(ra["ra_score"])
if score > raForcedFScoreCap {
t.Fatalf("forced F score should be capped at %d, got %d", raForcedFScoreCap, score)
}
if raAsString(ra["ra_level"]) != "F" {
t.Fatalf("forced F should have ra_level=F, got %s", raAsString(ra["ra_level"]))
}
}
func TestCalcRARatingXypModelRiskPoints(t *testing.T) {
cases := []struct {
name string
rating map[string]interface{}
want int
}{
{
name: "all miss",
rating: map[string]interface{}{
"xyp_model_score_high": "-1",
"xyp_model_score_mid": "-1",
"xyp_model_score_low": "-1",
},
want: 0,
},
{
name: "one low score",
rating: map[string]interface{}{
"xyp_model_score_high": "600",
"xyp_model_score_mid": "-1",
"xyp_model_score_low": "-1",
},
want: 50, // 650 - 600
},
{
name: "three low scores capped",
rating: map[string]interface{}{
"xyp_model_score_high": "400",
"xyp_model_score_mid": "400",
"xyp_model_score_low": "400",
},
want: 291, // (650-400)*3 = 750, capped per field at 97
},
{
name: "safe scores no deduction",
rating: map[string]interface{}{
"xyp_model_score_high": "900",
"xyp_model_score_mid": "800",
"xyp_model_score_low": "700",
},
want: 0,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := calcRARatingXypModelRiskPoints(tc.rating); got != tc.want {
t.Fatalf("got %d want %d", got, tc.want)
}
})
}
}
func TestRAVerifyScoreIncludesFraudSignals(t *testing.T) {
base := map[string]interface{}{
"triple": map[string]interface{}{"state": "1"},
"identity": map[string]interface{}{"result": 0},
"presence": map[string]interface{}{"desc": "正常", "status": 1},
"behavior": map[string]interface{}{"result": map[string]interface{}{"black_list": "0"}},
"fraud": nil,
"special": nil,
}
if got := calcRAVerifyScore(base); got != raScoreMax {
t.Fatalf("clean verify want %d, got %d", raScoreMax, got)
}
withComplaint := map[string]interface{}{
"triple": map[string]interface{}{"state": "1"},
"identity": map[string]interface{}{"result": 0},
"presence": map[string]interface{}{"desc": "正常", "status": 1},
"behavior": map[string]interface{}{"result": map[string]interface{}{"black_list": "0"}},
"complaint": map[string]interface{}{
"result": map[string]interface{}{"score": 11},
},
"fraud": nil,
"special": nil,
}
want := raScoreMax - raFraudPointsPerHit
if got := calcRAVerifyScore(withComplaint); got != want {
t.Fatalf("verify with fraud signal want %d, got %d", want, got)
}
if got := calcRAFraudScore(withComplaint); got != want {
t.Fatalf("fraud sub-score should match fraud-only deduction, want %d, got %d", want, got)
}
}
func TestRAScoreWeightedSum(t *testing.T) {
verify, judicial, credit := 920, 980, 760
wJ, wC := calcRADynamicWeights(judicial, credit)
// 980/760 均 < 1000触发司法+借贷双风险 → 70%/20%
want := int(math.Round(float64(verify)*raWeightVerify + float64(judicial)*wJ + float64(credit)*wC))
if want != 930 {
t.Fatalf("dual-risk example want 930, got %d (wJ=%.2f wC=%.2f)", want, wJ, wC)
}
verify, judicial, credit = 920, 1000, 1000
wJ, wC = calcRADynamicWeights(judicial, credit)
want = int(math.Round(float64(verify)*raWeightVerify + float64(judicial)*wJ + float64(credit)*wC))
if want != 992 {
t.Fatalf("baseline example want 992, got %d", want)
}
}

View File

@@ -0,0 +1,67 @@
package dwbg
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha"
)
// ProcessDWBG5SAMRequest DWBG5SAM 天远指迷报告
func ProcessDWBG5SAMRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.DWBG5SAMReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"name": encryptedName,
"idCard": encryptedIDCard,
"phone": encryptedMobileNo,
"accessoryUrl": paramsDto.AuthorizationURL,
}
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI112", reqData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
} else {
return nil, errors.Join(processors.ErrSystem, err)
}
}
// 过滤响应数据,删除指定字段
if respMap, ok := respData.(map[string]interface{}); ok {
delete(respMap, "reportUrl")
}
// 将响应数据转换为JSON字节
respBytes, err := json.Marshal(respData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -54,7 +54,7 @@ func ProcessDWBG6A2CRequest(ctx context.Context, params []byte, deps *processors
if respMap, ok := respData.(map[string]interface{}); ok { if respMap, ok := respData.(map[string]interface{}); ok {
delete(respMap, "reportUrl") delete(respMap, "reportUrl")
delete(respMap, "multCourtInfo") delete(respMap, "multCourtInfo")
delete(respMap, "judiciaRiskInfos") // delete(respMap, "judiciaRiskInfos")
} }
// 将响应数据转换为JSON字节 // 将响应数据转换为JSON字节

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
package dwbg
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/nuoer"
)
// ProcessDWBG9FB2Request DWBG9FB2 API处理方法 - 个人风险档案2
func ProcessDWBG9FB2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.DWBG9FB2Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
body := map[string]string{
"name": paramsDto.Name,
"idCard": paramsDto.IDCard,
"mobile": paramsDto.MobileNo,
}
nuoerDoCheckAPIKey := "gamaReportPageP02"
ApiPath := "/v1/doCheck"
resp, err := deps.NuoerService.CallAPI(ctx, nuoerDoCheckAPIKey, ApiPath, body)
if err != nil {
if errors.Is(err, nuoer.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
if errors.Is(err, nuoer.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
if respMap, ok := resp.Data.(map[string]interface{}); ok {
delete(respMap, "reportUrl")
if resultMap, ok := respMap["result"].(map[string]interface{}); ok {
delete(resultMap, "reportUrl")
}
}
respBytes, err := json.Marshal(resp.Data)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -0,0 +1,55 @@
package dwbg
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/nuoer"
)
// ProcessDWBG9FB3Request DWBG9FB3 API处理方法 - 个人风险档案1
func ProcessDWBG9FB3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.DWBG9FB3Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
body := map[string]string{
"name": paramsDto.Name,
"idCard": paramsDto.IDCard,
"mobile": paramsDto.MobileNo,
}
nuoerDoCheckAPIKey := "gamaReportPageP01"
ApiPath := "/v1/doCheck"
resp, err := deps.NuoerService.CallAPI(ctx, nuoerDoCheckAPIKey, ApiPath, body)
if err != nil {
if errors.Is(err, nuoer.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
if errors.Is(err, nuoer.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
if respMap, ok := resp.Data.(map[string]interface{}); ok {
delete(respMap, "reportUrl")
if resultMap, ok := respMap["result"].(map[string]interface{}); ok {
delete(resultMap, "reportUrl")
}
}
respBytes, err := json.Marshal(resp.Data)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -4,17 +4,15 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"log"
"tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors" "tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex" nuoerext "tyapi-server/internal/infrastructure/external/nuoer"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
) )
// ProcessFLXG0V4BRequest FLXG0V4B API处理方法 // ProcessFLXG0V4BRequest FLXG0V4B API处理方法 - 个人司法涉诉
func ProcessFLXG0V4BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { func ProcessFLXG0V4BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXG0V4BReq var paramsDto dto.FLXG0V4BReq
if err := json.Unmarshal(params, &paramsDto); err != nil { if err := json.Unmarshal(params, &paramsDto); err != nil {
@@ -24,73 +22,39 @@ func ProcessFLXG0V4BRequest(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil { if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" { if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空")) return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
} }
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil { body := map[string]string{
return nil, errors.Join(processors.ErrSystem, err) "name": paramsDto.Name,
"idCard": paramsDto.IDCard,
} }
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard) resp, err := deps.NuoerService.CallAPI(ctx, "personalLawsuit_cv1", "/v1/doCheck", body)
if err != nil { if err != nil {
return nil, errors.Join(processors.ErrSystem, err) if errors.Is(err, nuoerext.ErrDatasource) {
}
if deps.CallContext.ContractCode == "" {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, errors.New("合同编号不能为空"))
}
encryptedAuthAuthorizeFileCode, err := deps.WestDexService.Encrypt(deps.CallContext.ContractCode)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"idcard": encryptedIDCard,
"auth_authorizeFileCode": encryptedAuthAuthorizeFileCode,
"inquired_auth": fmt.Sprintf("authed:%s", paramsDto.AuthDate),
},
}
log.Println("reqData", reqData)
respBytes, err := deps.WestDexService.CallAPI(ctx, "G22SC01", reqData)
if err != nil {
// 数据源错误
if errors.Is(err, westdex.ErrDatasource) {
// 如果有返回内容,优先解析返回内容
if respBytes != nil {
parsed, parseErr := ParseJsonResponse(respBytes)
if parseErr == nil {
// 通过gjson获取指定路径的数据
contentResult := gjson.GetBytes(parsed, "G22SC0101.G22SC0102.content")
if contentResult.Exists() {
return []byte(contentResult.Raw), errors.Join(processors.ErrDatasource, err)
}
return parsed, errors.Join(processors.ErrDatasource, err)
}
// 解析失败,返回原始内容和系统错误
return respBytes, fmt.Errorf("%s: %w", processors.ErrSystem, parseErr)
}
// 没有返回内容,直接返回数据源错误
return nil, errors.Join(processors.ErrDatasource, err) return nil, errors.Join(processors.ErrDatasource, err)
} }
// 其他系统错误 if errors.Is(err, nuoerext.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)
} }
// 正常返回 - 不管有没有deps.Options.Json都进行ParseJsonResponse rawData, ok := resp.Data.(map[string]interface{})
parsed, parseErr := ParseJsonResponse(respBytes) if !ok {
if parseErr != nil { return processors.MarshalRawResponse(resp.Data)
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, parseErr)
} }
// 通过gjson获取指定路径的数据 result := mapFLXG0V4BResponse(rawData)
contentResult := gjson.GetBytes(parsed, "G22SC0101.G22SC0102.content")
if contentResult.Exists() { respBytes, err := json.Marshal(result)
return []byte(contentResult.Raw), nil if err != nil {
} else { return nil, errors.Join(processors.ErrSystem, err)
return nil, errors.Join(processors.ErrDatasource, err)
} }
return respBytes, nil
} }
// Content 数据内容 // Content 数据内容

View File

@@ -0,0 +1,170 @@
package flxg
const (
flxg0v4bMsgSuccess = "成功"
flxg0v4bMsgNotFound = "没有找到"
)
// mapFLXG0V4BResponse 将 nuoer 响应2.json / 2.md转为 FLXG0V4B 对外结构1.json / 1.md
func mapFLXG0V4BResponse(data map[string]interface{}) map[string]interface{} {
if data == nil {
return defaultFLXG0V4BResponse()
}
if _, ok := data["entout"]; ok {
return normalizeFLXG0V4BResponse(data)
}
flat := unwrapNuoerPersonalLawsuitData(data)
return buildFLXG0V4BResponse(flat)
}
func defaultFLXG0V4BResponse() map[string]interface{} {
return map[string]interface{}{
"entout": map[string]interface{}{"msg": flxg0v4bMsgNotFound},
"sxbzxr": map[string]interface{}{"msg": flxg0v4bMsgNotFound},
"xgbzxr": map[string]interface{}{"msg": flxg0v4bMsgNotFound},
}
}
func buildFLXG0V4BResponse(flat map[string]interface{}) map[string]interface{} {
return map[string]interface{}{
"entout": buildFLXG0V4BEntout(asMap(flat["lawsuitStat"])),
"sxbzxr": buildFLXG0V4BBzxrSection("sxbzxr", asSlice(flat["breachCaseList"])),
"xgbzxr": buildFLXG0V4BBzxrSection("xgbzxr", asSlice(flat["consumptionRestrictionList"])),
}
}
func normalizeFLXG0V4BResponse(data map[string]interface{}) map[string]interface{} {
out := map[string]interface{}{
"entout": normalizeFLXG0V4BEntoutSection(data["entout"]),
"sxbzxr": normalizeFLXG0V4BBzxrSection(data["sxbzxr"], "sxbzxr"),
"xgbzxr": normalizeFLXG0V4BBzxrSection(data["xgbzxr"], "xgbzxr"),
}
return out
}
func buildFLXG0V4BEntout(lawsuitStat map[string]interface{}) map[string]interface{} {
if !isLawsuitStatPayload(lawsuitStat) {
return map[string]interface{}{"msg": flxg0v4bMsgNotFound}
}
return map[string]interface{}{
"msg": flxg0v4bMsgSuccess,
"data": normalizeFLXG0V4FEntoutData(lawsuitStat),
}
}
func normalizeFLXG0V4BEntoutSection(raw interface{}) map[string]interface{} {
entout := asMap(raw)
if len(entout) == 0 {
return map[string]interface{}{"msg": flxg0v4bMsgNotFound}
}
if data := asMap(entout["data"]); len(data) > 0 {
entout["data"] = normalizeFLXG0V4FEntoutData(data)
if msg, _ := entout["msg"].(string); msg == "" {
entout["msg"] = flxg0v4bMsgSuccess
}
return entout
}
if isLawsuitStatPayload(entout) {
return map[string]interface{}{
"msg": flxg0v4bMsgSuccess,
"data": normalizeFLXG0V4FEntoutData(entout),
}
}
if msg, _ := entout["msg"].(string); msg != "" {
return map[string]interface{}{"msg": msg}
}
return map[string]interface{}{"msg": flxg0v4bMsgNotFound}
}
func normalizeFLXG0V4FEntoutData(src map[string]interface{}) map[string]interface{} {
if len(src) == 0 {
return map[string]interface{}{}
}
dst := make(map[string]interface{}, len(src))
for k, v := range src {
switch k {
case "cases_tree":
dst[k] = normalizeFLXG0V4FCasesTree(asMap(v))
case "preservation", "administrative", "civil", "implement", "criminal", "bankrupt":
dst[k] = normalizeFLXG0V4FCaseSection(asMap(v))
default:
dst[k] = v
}
}
return dst
}
func normalizeFLXG0V4FCasesTree(src map[string]interface{}) map[string]interface{} {
if len(src) == 0 {
return map[string]interface{}{}
}
dst := make(map[string]interface{}, len(src))
for _, key := range []string{"administrative", "criminal", "civil"} {
if v, ok := src[key]; ok {
dst[key] = asSlice(v)
}
}
return dst
}
func normalizeFLXG0V4FCaseSection(src map[string]interface{}) map[string]interface{} {
if len(src) == 0 {
return map[string]interface{}{}
}
if _, hasCases := src["cases"]; !hasCases {
if _, hasCount := src["count"]; !hasCount {
return map[string]interface{}{}
}
}
dst := make(map[string]interface{}, 2)
if v, ok := src["cases"]; ok {
dst["cases"] = asSlice(v)
}
if v, ok := src["count"]; ok {
dst["count"] = asMap(v)
}
return dst
}
func buildFLXG0V4BBzxrSection(key string, list []interface{}) map[string]interface{} {
if len(list) == 0 {
return map[string]interface{}{"msg": flxg0v4bMsgNotFound}
}
return map[string]interface{}{
"data": map[string]interface{}{
key: list,
},
}
}
func normalizeFLXG0V4BBzxrSection(raw interface{}, key string) map[string]interface{} {
section := asMap(raw)
if len(section) == 0 {
return map[string]interface{}{"msg": flxg0v4bMsgNotFound}
}
if data := asMap(section["data"]); len(data) > 0 {
if arr := asSlice(data[key]); len(arr) > 0 {
return map[string]interface{}{
"data": map[string]interface{}{key: arr},
}
}
}
if arr := asSlice(section[key]); len(arr) > 0 {
return map[string]interface{}{
"data": map[string]interface{}{key: arr},
}
}
if msg, _ := section["msg"].(string); msg != "" {
return map[string]interface{}{"msg": msg}
}
return map[string]interface{}{"msg": flxg0v4bMsgNotFound}
}

View File

@@ -20,7 +20,9 @@ func ProcessFLXG3A9BRequest(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil { if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
if paramsDto.IDCard == "410482198504029333" {
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name) encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil { if err != nil {
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)

View File

@@ -0,0 +1,59 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha"
)
// ProcessFLXG5A3BCOPYRequest FLXG5A3B COPY API处理方法 - 个人司法涉诉
func ProcessFLXG5A3BCOPYRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXG5A3BReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"name": encryptedName,
"idCard": encryptedIDCard,
"authorized": paramsDto.Authorized,
}
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI006", reqData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
} else {
return nil, errors.Join(processors.ErrSystem, err)
}
}
// 将响应数据转换为JSON字节
respBytes, err := json.Marshal(respData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -7,7 +7,7 @@ import (
"tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors" "tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha" "tyapi-server/internal/infrastructure/external/nuoer"
) )
// ProcessFLXG5A3BRequest FLXG5A3B API处理方法 - 个人司法涉诉 // ProcessFLXG5A3BRequest FLXG5A3B API处理方法 - 个人司法涉诉
@@ -20,36 +20,37 @@ func ProcessFLXG5A3BRequest(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil { if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550"{ if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空")) return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
} }
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil { body := map[string]string{
return nil, errors.Join(processors.ErrSystem, err) "name": paramsDto.Name,
"idCard": paramsDto.IDCard,
} }
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard) nuoerDoCheckAPIKey := "personalLawsuit_cv1"
if err != nil { ApiPath := "/v1/doCheck"
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{ resp, err := deps.NuoerService.CallAPI(ctx, nuoerDoCheckAPIKey, ApiPath, body)
"name": encryptedName,
"idCard": encryptedIDCard,
"authorized": paramsDto.Authorized,
}
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI006", reqData)
if err != nil { if err != nil {
if errors.Is(err, zhicha.ErrDatasource) { if errors.Is(err, nuoer.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err) return nil, errors.Join(processors.ErrDatasource, err)
} else {
return nil, errors.Join(processors.ErrSystem, err)
} }
if errors.Is(err, nuoer.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err)
} }
// 将响应数据转换为JSON字节 rawData, ok := resp.Data.(map[string]interface{})
respBytes, err := json.Marshal(respData) if !ok {
return processors.MarshalRawResponse(resp.Data)
}
result := mapNuoerPersonalLawsuitToResponse(rawData)
respBytes, err := json.Marshal(result)
if err != nil { if err != nil {
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)
} }

View File

@@ -0,0 +1,177 @@
package flxg
// mapNuoerPersonalLawsuitToResponse 将 nuoer 响应转为对外结构2.md
// lawsuitStat -> entout, breachCaseList -> sxbzxr, consumptionRestrictionList -> xgbzxr
func mapNuoerPersonalLawsuitToResponse(data map[string]interface{}) map[string]interface{} {
if data == nil {
return defaultFLXG5A3BResponse()
}
if _, hasEntout := data["entout"]; hasEntout {
return data
}
flat := unwrapNuoerPersonalLawsuitData(data)
return map[string]interface{}{
"sxbzxr": asSlice(flat["breachCaseList"]),
"xgbzxr": asSlice(flat["consumptionRestrictionList"]),
"entout": pickEntout(flat["lawsuitStat"]),
}
}
func defaultFLXG5A3BResponse() map[string]interface{} {
return map[string]interface{}{
"sxbzxr": []interface{}{},
"entout": map[string]interface{}{},
"xgbzxr": []interface{}{},
}
}
func pickEntout(v interface{}) interface{} {
if v == nil {
return map[string]interface{}{}
}
return v
}
// unwrapNuoerPersonalLawsuitData 解包 nuoer 嵌套结构 data.result.detail[],提取 1.md 平铺字段。
func unwrapNuoerPersonalLawsuitData(data map[string]interface{}) map[string]interface{} {
if _, ok := data["lawsuitStat"]; ok {
return data
}
if result, ok := data["result"].(map[string]interface{}); ok {
for _, item := range asSlice(result["detail"]) {
if flat := extractFromNuoerDetailItem(asMap(item)); len(flat) > 0 {
return flat
}
}
}
return data
}
func extractFromNuoerDetailItem(item map[string]interface{}) map[string]interface{} {
if len(item) == 0 {
return nil
}
lawsuitStat := extractLawsuitStatFromDetailItem(item)
if !isLawsuitStatPayload(asMap(lawsuitStat)) {
return nil
}
return map[string]interface{}{
"lawsuitStat": lawsuitStat,
"breachCaseList": extractBreachCaseRecords(item),
"consumptionRestrictionList": extractConsumptionRestrictionRecords(item),
}
}
func extractLawsuitStatFromDetailItem(item map[string]interface{}) interface{} {
// nuoer 真实涉诉数据通常在 breachCaseList/consumptionRestrictionList 的包装对象内
for _, v := range asSlice(item["breachCaseList"]) {
if m := asMap(v); m != nil {
if ls := asMap(m["lawsuitStat"]); isLawsuitStatPayload(ls) {
return ls
}
}
}
for _, v := range asSlice(item["consumptionRestrictionList"]) {
if m := asMap(v); m != nil {
if ls := asMap(m["lawsuitStat"]); isLawsuitStatPayload(ls) {
return ls
}
}
}
if lsWrap := asMap(item["lawsuitStat"]); lsWrap != nil {
if ls := extractLawsuitStatFromWrapper(lsWrap); ls != nil {
return ls
}
}
return map[string]interface{}{}
}
func extractLawsuitStatFromWrapper(wrap map[string]interface{}) map[string]interface{} {
if isLawsuitStatPayload(wrap) {
return wrap
}
for _, v := range asSlice(wrap["detail"]) {
if m := asMap(v); m != nil {
if ls := asMap(m["lawsuitStat"]); isLawsuitStatPayload(ls) {
return ls
}
if isLawsuitStatPayload(m) {
return m
}
}
}
return nil
}
func isLawsuitStatPayload(m map[string]interface{}) bool {
if len(m) == 0 {
return false
}
for _, k := range []string{"criminal", "civil", "administrative", "cases_tree", "count", "crc", "implement", "preservation", "bankrupt"} {
if _, ok := m[k]; ok {
return true
}
}
return false
}
func extractBreachCaseRecords(item map[string]interface{}) []interface{} {
out := make([]interface{}, 0)
collectBreachCaseRecords(asSlice(item["breachCaseList"]), &out)
if lsWrap := asMap(item["lawsuitStat"]); lsWrap != nil {
for _, v := range asSlice(lsWrap["detail"]) {
collectBreachCaseRecords(asSlice(asMap(v)["breachCaseList"]), &out)
}
}
return out
}
func collectBreachCaseRecords(list []interface{}, out *[]interface{}) {
for _, v := range list {
m := asMap(v)
if len(m) == 0 {
continue
}
if _, isWrapper := m["lawsuitStat"]; isWrapper {
if inner := asSlice(m["breachCaseList"]); len(inner) > 0 {
collectBreachCaseRecords(inner, out)
}
continue
}
if _, hasAh := m["ah"]; hasAh {
*out = append(*out, m)
}
}
}
func extractConsumptionRestrictionRecords(item map[string]interface{}) []interface{} {
out := make([]interface{}, 0)
collectConsumptionRestrictionRecords(asSlice(item["consumptionRestrictionList"]), &out)
if lsWrap := asMap(item["lawsuitStat"]); lsWrap != nil {
for _, v := range asSlice(lsWrap["detail"]) {
collectConsumptionRestrictionRecords(asSlice(asMap(v)["consumptionRestrictionList"]), &out)
}
}
return out
}
func collectConsumptionRestrictionRecords(list []interface{}, out *[]interface{}) {
for _, v := range list {
m := asMap(v)
if len(m) == 0 {
continue
}
if _, isWrapper := m["lawsuitStat"]; isWrapper {
if inner := asSlice(m["consumptionRestrictionList"]); len(inner) > 0 {
collectConsumptionRestrictionRecords(inner, out)
}
continue
}
if _, hasAh := m["ah"]; hasAh {
*out = append(*out, m)
}
}
}

View File

@@ -7,7 +7,7 @@ import (
"tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors" "tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/xingwei" nuoerext "tyapi-server/internal/infrastructure/external/nuoer"
) )
// ProcessFLXG7E8FRequest FLXG7E8F API处理方法 - 个人司法数据查询 // ProcessFLXG7E8FRequest FLXG7E8F API处理方法 - 个人司法数据查询
@@ -20,29 +20,38 @@ func ProcessFLXG7E8FRequest(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil { if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" { if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空")) return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
} }
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
reqData := map[string]interface{}{ body := map[string]string{
"name": paramsDto.Name, "name": paramsDto.Name,
"idCardNum": paramsDto.IDCard, "idCard": paramsDto.IDCard,
"phoneNumber": paramsDto.MobileNo,
} }
// 调用行为数据API使用指定的project_id resp, err := deps.NuoerService.CallAPI(ctx, "personalLawsuit_cv1", "/v1/doCheck", body)
projectID := "CDJ-1101695378264092672"
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
if err != nil { if err != nil {
if errors.Is(err, xingwei.ErrNotFound) { if errors.Is(err, nuoerext.ErrDatasource) {
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, xingwei.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err) return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, xingwei.ErrSystem) {
return nil, errors.Join(processors.ErrSystem, err)
} else {
return nil, errors.Join(processors.ErrSystem, err)
} }
if errors.Is(err, nuoerext.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
rawData, ok := resp.Data.(map[string]interface{})
if !ok {
return processors.MarshalRawResponse(resp.Data)
}
result := map[string]interface{}{
"judicial_data": mapFLXG7E8FToJudicialData(rawData),
}
respBytes, err := json.Marshal(result)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
} }
return respBytes, nil return respBytes, nil

View File

@@ -0,0 +1,206 @@
package flxg
// mapFLXG7E8FToJudicialData 将 nuoer 响应2.json / 2.md转为 FLXG7E8F 对外结构1.json / 1.md
func mapFLXG7E8FToJudicialData(data map[string]interface{}) map[string]interface{} {
if data == nil {
return defaultFLXG7E8FJudicialData()
}
if _, ok := data["lawsuitStat"]; ok {
return buildFLXG7E8FJudicialData(data)
}
if _, ok := data["judicial_data"]; ok {
if jd := asMap(data["judicial_data"]); len(jd) > 0 {
return buildFLXG7E8FJudicialData(jd)
}
}
flat := unwrapNuoerPersonalLawsuitData(data)
return buildFLXG7E8FJudicialData(flat)
}
func defaultFLXG7E8FJudicialData() map[string]interface{} {
return map[string]interface{}{
"consumptionRestrictionList": []interface{}{},
"breachCaseList": []interface{}{},
"lawsuitStat": defaultFLXG7E8FLawsuitStat(),
}
}
func buildFLXG7E8FJudicialData(flat map[string]interface{}) map[string]interface{} {
return map[string]interface{}{
"consumptionRestrictionList": mapFLXG7E8FConsumptionRestrictionList(asSlice(flat["consumptionRestrictionList"])),
"breachCaseList": mapFLXG7E8FBreachCaseList(asSlice(flat["breachCaseList"])),
"lawsuitStat": normalizeFLXG7E8FLawsuitStat(asMap(flat["lawsuitStat"])),
}
}
func mapFLXG7E8FConsumptionRestrictionList(items []interface{}) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(items))
for _, item := range items {
m := asMap(item)
if len(m) == 0 {
continue
}
if _, ok := m["caseNumber"]; ok {
result = append(result, m)
continue
}
result = append(result, map[string]interface{}{
"caseNumber": m["ah"],
"id": m["id"],
"issueDate": m["fbrq"],
"executiveCourt": m["zxfy"],
"fileDate": m["larq"],
})
}
return result
}
func mapFLXG7E8FBreachCaseList(items []interface{}) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(items))
for _, item := range items {
m := asMap(item)
if len(m) == 0 {
continue
}
if _, ok := m["caseNumber"]; ok {
result = append(result, m)
continue
}
result = append(result, map[string]interface{}{
"caseNumber": m["ah"],
"issueDate": m["fbrq"],
"id": m["id"],
"fileDate": m["larq"],
"fulfillStatus": m["lxqk"],
"estimatedJudgementAmount": m["pjje_gj"],
"province": m["sf"],
"sex": m["xb"],
"concreteDetails": m["xwqx"],
"obligation": m["yw"],
"executiveCourt": m["zxfy"],
"enforcementBasisOrganization": m["zxyjdw"],
"enforcementBasisNumber": m["zxyjwh"],
})
}
return result
}
func normalizeFLXG7E8FLawsuitStat(src map[string]interface{}) map[string]interface{} {
lawsuitStat := defaultFLXG7E8FLawsuitStat()
for k, v := range src {
switch k {
case "cases_tree":
lawsuitStat[k] = normalizeFLXG7E8FCasesTree(asMap(v))
case "count":
lawsuitStat[k] = normalizeFLXG7E8FCount(asMap(v))
case "preservation", "administrative", "civil", "implement", "criminal", "bankrupt":
lawsuitStat[k] = normalizeFLXG7E8FCaseSection(asMap(v))
default:
lawsuitStat[k] = v
}
}
return lawsuitStat
}
func defaultFLXG7E8FLawsuitStat() map[string]interface{} {
return map[string]interface{}{
"crc": 0,
"cases_tree": normalizeFLXG7E8FCasesTree(map[string]interface{}{}),
"count": normalizeFLXG7E8FCount(map[string]interface{}{}),
"preservation": normalizeFLXG7E8FCaseSection(map[string]interface{}{}),
"administrative": normalizeFLXG7E8FCaseSection(map[string]interface{}{}),
"civil": normalizeFLXG7E8FCaseSection(map[string]interface{}{}),
"implement": normalizeFLXG7E8FCaseSection(map[string]interface{}{}),
"criminal": normalizeFLXG7E8FCaseSection(map[string]interface{}{}),
"bankrupt": normalizeFLXG7E8FCaseSection(map[string]interface{}{}),
}
}
func normalizeFLXG7E8FCasesTree(src map[string]interface{}) map[string]interface{} {
dst := map[string]interface{}{
"administrative": []interface{}{},
"criminal": []interface{}{},
"civil": []interface{}{},
}
for _, key := range []string{"administrative", "criminal", "civil"} {
if v, ok := src[key]; ok {
dst[key] = asSlice(v)
}
}
return dst
}
func normalizeFLXG7E8FCaseSection(src map[string]interface{}) map[string]interface{} {
dst := map[string]interface{}{
"cases": []interface{}{},
"count": normalizeFLXG7E8FCount(map[string]interface{}{}),
}
if v, ok := src["cases"]; ok {
dst["cases"] = asSlice(v)
}
if v, ok := src["count"]; ok {
dst["count"] = normalizeFLXG7E8FCount(asMap(v))
}
return dst
}
func normalizeFLXG7E8FCount(src map[string]interface{}) map[string]interface{} {
dst := map[string]interface{}{
"money_yuangao": 0,
"area_stat": "",
"count_jie_beigao": 0,
"count_total": 0,
"money_wei_yuangao": 0,
"count_wei_total": 0,
"money_wei_beigao": 0,
"count_other": 0,
"money_beigao": 0,
"count_yuangao": 0,
"money_jie_other": 0,
"money_total": 0,
"money_wei_total": 0,
"count_wei_yuangao": 0,
"ay_stat": "",
"count_beigao": 0,
"money_jie_yuangao": 0,
"jafs_stat": "",
"money_jie_beigao": 0,
"count_wei_beigao": 0,
"count_jie_other": 0,
"count_jie_total": 0,
"count_wei_other": 0,
"money_other": 0,
"count_jie_yuangao": 0,
"money_jie_total": 0,
"money_wei_other": 0,
"money_wei_percent": 0,
"larq_stat": "",
}
for k, v := range src {
dst[k] = v
}
return dst
}
func asMap(v interface{}) map[string]interface{} {
if v == nil {
return map[string]interface{}{}
}
if m, ok := v.(map[string]interface{}); ok {
return m
}
return map[string]interface{}{}
}
func asSlice(v interface{}) []interface{} {
if v == nil {
return []interface{}{}
}
if s, ok := v.([]interface{}); ok {
return s
}
return []interface{}{}
}

View File

@@ -22,33 +22,24 @@ func ProcessFLXG8B4DRequest(ctx context.Context, params []byte, deps *processors
return nil, errors.Join(processors.ErrInvalidParam, err) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
// 三选一校验:MobileNo、IDCard、BankCard 必须且只能有一个 // MobileNo、IDCard、BankCard 中按优先级选择一个MobileNo > IDCard > BankCard
var fieldCount int
var selectedField string var selectedField string
var selectedValue string var selectedValue string
if paramsDto.MobileNo != "" { if paramsDto.MobileNo != "" {
fieldCount++
selectedField = "mobile_no" selectedField = "mobile_no"
selectedValue = paramsDto.MobileNo selectedValue = paramsDto.MobileNo
} } else if paramsDto.IDCard != "" {
if paramsDto.IDCard != "" {
fieldCount++
selectedField = "id_card" selectedField = "id_card"
selectedValue = paramsDto.IDCard selectedValue = paramsDto.IDCard
} } else if paramsDto.BankCard != "" {
if paramsDto.BankCard != "" {
fieldCount++
selectedField = "bank_card" selectedField = "bank_card"
selectedValue = paramsDto.BankCard selectedValue = paramsDto.BankCard
} }
if fieldCount == 0 { if selectedField == "" {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("必须提供手机号、身份证号或银行卡号中的其中一个")) return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("必须提供手机号、身份证号或银行卡号中的其中一个"))
} }
if fieldCount > 1 {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("只能提供手机号、身份证号或银行卡号中的一个,不能同时提供多个"))
}
// 只对选中的字段进行加密 // 只对选中的字段进行加密
var encryptedValue string var encryptedValue string
@@ -77,12 +68,12 @@ func ProcessFLXG8B4DRequest(ctx context.Context, params []byte, deps *processors
} }
switch selectedField { switch selectedField {
case "mobile_no":
reqData["phone"] = encryptedValue
case "id_card": case "id_card":
reqData["idCard"] = encryptedValue reqData["idCard"] = encryptedValue
case "bank_card": case "bank_card":
reqData["name"] = encryptedValue reqData["name"] = encryptedValue
case "mobile_no":
reqData["phone"] = encryptedValue
} }
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI027", reqData) respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI027", reqData)

View File

@@ -20,7 +20,9 @@ func ProcessFLXG9C1DRequest(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil { if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
if paramsDto.IDCard == "410482198504029333" {
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name) encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil { if err != nil {
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)

View File

@@ -20,7 +20,7 @@ func ProcessFLXGCA3DRequest(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil { if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" { if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空")) return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
} }
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name) encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)

View File

@@ -20,6 +20,10 @@ func ProcessFLXGDEA8Request(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil { if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
// 去掉司法案件案件去掉身份证号码
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name) encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil { if err != nil {

View File

@@ -7,7 +7,7 @@ import (
"tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors" "tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha" "tyapi-server/internal/infrastructure/external/nuoer"
) )
// ProcessFLXGDEA9Request FLXGDEA9 API处理方法 // ProcessFLXGDEA9Request FLXGDEA9 API处理方法
@@ -21,34 +21,36 @@ func ProcessFLXGDEA9Request(ctx context.Context, params []byte, deps *processors
return nil, errors.Join(processors.ErrInvalidParam, err) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name) if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550"{
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空")) return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
} }
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard) body := map[string]string{
"name": paramsDto.Name,
"idCard": paramsDto.IDCard,
}
nuoerDoCheckAPIKey := "idRiskTagV106"
ApiPath := "/v1/doCheck"
resp, err := deps.NuoerService.CallAPI(ctx, nuoerDoCheckAPIKey, ApiPath, body)
if err != nil { if err != nil {
if errors.Is(err, nuoer.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
if errors.Is(err, nuoer.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)
} }
reqData := map[string]interface{}{
"name": encryptedName, rawData, ok := resp.Data.(map[string]interface{})
"idCard": encryptedIDCard, if !ok {
"authorized": paramsDto.Authorized, return processors.MarshalRawResponse(resp.Data)
} }
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI005", reqData) result := mapNuoerIdRiskToResponse(rawData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
} else {
return nil, errors.Join(processors.ErrSystem, err)
}
}
// 将响应数据转换为JSON字节 respBytes, err := json.Marshal(result)
respBytes, err := json.Marshal(respData)
if err != nil { if err != nil {
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)
} }

View File

@@ -0,0 +1,164 @@
package flxg
import (
"fmt"
"strconv"
"strings"
)
// riskCode 位序(从左至右 1-25到 level 代码映射,见 1.md 与风险类型代码表。
// 多位命中时去重后以逗号拼接,如 "A,B"。
var riskCodeBitLevelMappings = map[int]string{
// A — 前科:侵犯公民人身权利、民主权利(在逃、盗窃、诈骗、抢劫、故意伤害、强奸等在刑或前科等)
1: "A", // 是否 SX涉刑
11: "A", // 近5年其他 SX 案
12: "A", // 5年前 SX 案
16: "A", // 是否 ZT在逃
// B — 经济类前科(破坏金融秩序、非法吸存、违发贷款、金融诈骗、集资诈骗、保险诈骗、假币等在刑或前科等)
2: "B2", // 近5年破坏金融管理秩序
3: "B", // 近5年非法吸收公众存款
4: "B", // 近5年违法发放贷款
5: "B4", // 近5年洗钱
6: "B3", // 近5年金融诈骗
7: "B3", // 近5年集资诈骗
8: "B3", // 近5年贷款诈骗
9: "B3", // 近5年信用卡诈骗
10: "B3", // 近5年保险诈骗
// C — 妨害社会管理秩序(扰乱公共秩序、妨害司法、涉毒、涉黄等在刑或前科等)
14: "C3", // 是否 SD涉毒
15: "C3", // 是否 XD吸毒
// D — 重点(危害国家、公共安全,涉恐、疆藏,涉稳、涉黑、涉及境外等)
13: "D", // 是否 SA涉案
17: "D", // 是否 ZD重点
18: "D4", // 是否 SK 人员(涉恐、疆藏)
19: "D2", // 是否 SW 人员(涉稳)
20: "D5", // 是否其他 ZD 人员(涉黑)
// E — 涉交通案件(危险驾驶、交通肇事等)
21: "E", // 是否 SJTLAJ
22: "E", // 近5年危险驾驶
23: "E", // 近5年交通肇事
24: "E", // 5年前 SJTLAJ
}
// mapNuoerIdRiskToResponse 将 nuoer 响应2json.md转为对外结构1json.md
// 解包 result将 riskCode 转为 level。
func mapNuoerIdRiskToResponse(data map[string]interface{}) map[string]interface{} {
if data == nil {
return map[string]interface{}{"level": "0"}
}
if level, ok := data["level"]; ok && level != nil {
if s := strings.TrimSpace(stringifyRiskCodeVal(level)); s != "" {
return map[string]interface{}{"level": s}
}
}
payload := unwrapNuoerIdRiskData(data)
riskCode := strings.TrimSpace(stringifyRiskCodeVal(payload["riskCode"]))
return map[string]interface{}{
"level": mapRiskCodeToLevel(riskCode),
}
}
func unwrapNuoerIdRiskData(data map[string]interface{}) map[string]interface{} {
if _, ok := data["riskCode"]; ok {
return data
}
if result, ok := data["result"].(map[string]interface{}); ok {
return result
}
return data
}
func mapRiskCodeToLevel(riskCode string) string {
if riskCode == "" {
return "0"
}
codes := make([]string, 0, 8)
for i, ch := range riskCode {
if ch != '1' {
continue
}
pos := i + 1
if pos == 25 {
continue
}
if code, ok := riskCodeBitLevelMappings[pos]; ok && code != "" {
codes = append(codes, code)
}
}
codes = dedupeLevelCodes(codes)
codes = collapseParentLevelCodes(codes)
if len(codes) == 0 {
return "0"
}
return strings.Join(codes, ",")
}
func dedupeLevelCodes(codes []string) []string {
if len(codes) == 0 {
return codes
}
seen := make(map[string]struct{}, len(codes))
out := make([]string, 0, len(codes))
for _, code := range codes {
if _, ok := seen[code]; ok {
continue
}
seen[code] = struct{}{}
out = append(out, code)
}
return out
}
func collapseParentLevelCodes(codes []string) []string {
if len(codes) == 0 {
return codes
}
hasChild := func(parent string) bool {
for _, code := range codes {
if code != parent && strings.HasPrefix(code, parent) {
return true
}
}
return false
}
out := make([]string, 0, len(codes))
for _, code := range codes {
switch code {
case "A", "B", "C", "D":
if hasChild(code) {
continue
}
}
out = append(out, code)
}
return out
}
func stringifyRiskCodeVal(v interface{}) string {
if v == nil {
return ""
}
switch val := v.(type) {
case string:
return val
case float64:
if val == float64(int64(val)) {
return strconv.FormatInt(int64(val), 10)
}
return strconv.FormatFloat(val, 'f', -1, 64)
case int:
return strconv.Itoa(val)
case int64:
return strconv.FormatInt(val, 10)
default:
return fmt.Sprint(val)
}
}

View File

@@ -0,0 +1,54 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shujubao"
)
// ProcessFLXGDJG3Request FLXGDJG3 董监高司法综合信息核验 API 处理方法(使用数据宝服务示例)
func ProcessFLXGDJG3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXGDJG3Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
// 构建数据宝入参sign 外的业务参数可按需 AES 加密后作为 bodyData
reqParams := map[string]interface{}{
"key": "1cce582f0a6f3ca40de80f1bea9b9698",
"idcard": paramsDto.IDCard,
}
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
apiPath := "/communication/personal/10166"
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
if err != nil {
if errors.Is(err, shujubao.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
if errors.Is(err, shujubao.ErrQueryEmpty) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
// 解析响应中的 JSON 字符串(使用 qyglb4c0 中的 RecursiveParse
parsedResp, err := RecursiveParse(data)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
respBytes, err := json.Marshal(parsedResp)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -0,0 +1,52 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/haiyuapi"
)
// 去掉身份证去掉身份证
// ProcessFLXGHB4FRequest FLXGHB4F API处理方法 - 个人涉诉案件查询海宇API
func ProcessFLXGHB4FRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXGHB4FReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
if deps.HaiyuapiService == nil {
return nil, errors.Join(processors.ErrSystem, errors.New("海宇API服务未初始化"))
}
// 去掉司法案件案件去掉身份证号码
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
}
reqParams := map[string]interface{}{
"name": paramsDto.Name,
"id_card": paramsDto.IDCard,
"auth_pdf_base64": paramsDto.AuthAuthorizeFileBase64,
}
apiPath := "/api/v1/FLXGHB4F"
respBytes, err := deps.HaiyuapiService.CallAPI(ctx, apiPath, reqParams)
if err != nil {
if errors.Is(err, haiyuapi.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
}
if errors.Is(err, haiyuapi.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -20,7 +20,9 @@ func ProcessFLXGK5D2Request(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil { if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
if paramsDto.IDCard == "410482198504029333" {
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name) encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil { if err != nil {
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)

View File

@@ -0,0 +1,63 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shujubao"
)
// ProcessIVYZ18HYRequest IVYZ18HY 婚姻状况核验V2单人 API 处理方法(使用数据宝服务示例)
func ProcessIVYZ18HYRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ18HYReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
fixedData := map[string]interface{}{"msg": "请联系商务咨询"}
fixedRespBytes, err := json.Marshal(fixedData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return fixedRespBytes, nil
authDate := ""
if len(paramsDto.AuthDate) >= 8 {
authDate = paramsDto.AuthDate[len(paramsDto.AuthDate)-8:]
}
reqParams := map[string]interface{}{
"key": "",
"idcard": paramsDto.IDCard,
"name": paramsDto.Name,
"maritalType": paramsDto.MaritalType,
"authcode": paramsDto.AuthAuthorizeFileBase64,
"authAuthorizeFileCode": paramsDto.AuthAuthorizeFileCode,
"authDate": authDate,
}
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
apiPath := "/communication/personal/10333"
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
if err != nil {
if errors.Is(err, shujubao.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
if errors.Is(err, shujubao.ErrQueryEmpty) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
respBytes, err := json.Marshal(data)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -0,0 +1,54 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shujubao"
)
// ProcessIVYZ28HYRequest IVYZ28HY 婚姻状况核验单人) API 处理方法(使用数据宝服务示例)
func ProcessIVYZ28HYRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ28HYReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
fixedData := map[string]interface{}{"msg": "请联系商务咨询"}
fixedRespBytes, err := json.Marshal(fixedData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return fixedRespBytes, nil
reqParams := map[string]interface{}{
"key": "",
"idcard": paramsDto.IDCard,
"name": paramsDto.Name,
}
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
apiPath := "/communication/personal/10149"
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
if err != nil {
if errors.Is(err, shujubao.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
if errors.Is(err, shujubao.ErrQueryEmpty) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
respBytes, err := json.Marshal(data)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -4,13 +4,26 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"strconv"
"strings"
"time"
"tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors" "tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha" "tyapi-server/internal/infrastructure/external/shumai"
) )
// ProcessIVYZ2A8BRequest IVYZ2A8B API处理方法 - 身份二要素认证 // ivyz2a8bShumaiResp 数脉 /v4/id_card/check 返回格式
type ivyz2a8bShumaiResp struct {
Result float64 `json:"result"`
OrderNo string `json:"order_no"`
Desc string `json:"desc"`
Sex string `json:"sex"`
Birthday string `json:"birthday"` // yyyyMMdd
Address string `json:"address"`
}
// ProcessIVYZ2A8BRequest IVYZ2A8B API处理方法 - 身份二要素认证政务版 数脉内部替换
func ProcessIVYZ2A8BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { func ProcessIVYZ2A8BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ2A8BReq var paramsDto dto.IVYZ2A8BReq
if err := json.Unmarshal(params, &paramsDto); err != nil { if err := json.Unmarshal(params, &paramsDto); err != nil {
@@ -20,37 +33,129 @@ func ProcessIVYZ2A8BRequest(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil { if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
reqFormData := map[string]interface{}{
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name) "idcard": paramsDto.IDCard,
if err != nil { "name": paramsDto.Name,
return nil, errors.Join(processors.ErrSystem, err)
} }
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard) // 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
if err != nil { apiPath := "/v4/id_card/check" // 接口路径,根据数脉文档填写(如 v4/xxx
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{ // 先尝试使用政务接口app_id2 和 app_secret2
"name": encryptedName, respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, true)
"idCard": encryptedIDCard,
"authorized": paramsDto.Authorized,
}
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI001", reqData)
if err != nil { if err != nil {
if errors.Is(err, zhicha.ErrDatasource) { // 使用实时接口app_id 和 app_secret重试
return nil, errors.Join(processors.ErrDatasource, err) respBytes, err = deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, false)
} else { // 如果重试后仍然失败,或者原本就是查无记录错误,返回错误
return nil, errors.Join(processors.ErrSystem, err) if err != nil {
if errors.Is(err, shumai.ErrNotFound) {
// 查无记录情况
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
} }
} }
// 将响应数据转换为JSON字节 // 将数脉返回的新格式映射为原有 API 输出格式
respBytes, err := json.Marshal(respData) oldFormat, err := mapIVYZ2A8BShumaiToOld(respBytes)
if err != nil { if err != nil {
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)
} }
return json.Marshal(oldFormat)
return respBytes, nil }
func mapIVYZ2A8BShumaiToOld(respBytes []byte) (map[string]interface{}, error) {
var r ivyz2a8bShumaiResp
if err := json.Unmarshal(respBytes, &r); err != nil {
return nil, err
}
// final_auth_result: "0"=一致,"1"=不一致/无记录;按 result0-一致1-不一致2-无记录(预留)
finalAuth := "1"
switch int(r.Result) {
case 0:
finalAuth = "0"
}
return map[string]interface{}{
"final_auth_result": finalAuth,
"birthday": formatBirthdayOld(r.Birthday),
"address": r.Address,
"constellation": constellationFromBirthday(r.Birthday),
"gender": r.Sex,
"age": ageFromBirthday(r.Birthday),
}, nil
}
func formatBirthdayOld(s string) string {
s = strings.TrimSpace(s)
if len(s) != 8 {
return s
}
for _, c := range s {
if c < '0' || c > '9' {
return s
}
}
return s[0:4] + "年" + s[4:6] + "月" + s[6:8] + "日"
}
func constellationFromBirthday(s string) string {
if len(s) != 8 {
return ""
}
month, _ := strconv.Atoi(s[4:6])
day, _ := strconv.Atoi(s[6:8])
if month < 1 || month > 12 || day < 1 || day > 31 {
return ""
}
switch {
case (month == 12 && day >= 22) || (month == 1 && day <= 19):
return "魔羯座"
case (month == 1 && day >= 20) || (month == 2 && day <= 18):
return "水瓶座"
case (month == 2 && day >= 19) || (month == 3 && day <= 20):
return "双鱼座"
case (month == 3 && day >= 21) || (month == 4 && day <= 19):
return "白羊座"
case (month == 4 && day >= 20) || (month == 5 && day <= 20):
return "金牛座"
case (month == 5 && day >= 21) || (month == 6 && day <= 21):
return "双子座"
case (month == 6 && day >= 22) || (month == 7 && day <= 22):
return "巨蟹座"
case (month == 7 && day >= 23) || (month == 8 && day <= 22):
return "狮子座"
case (month == 8 && day >= 23) || (month == 9 && day <= 22):
return "处女座"
case (month == 9 && day >= 23) || (month == 10 && day <= 23):
return "天秤座"
case (month == 10 && day >= 24) || (month == 11 && day <= 22):
return "天蝎座"
case (month == 11 && day >= 23) || (month == 12 && day <= 21):
return "射手座"
default:
return "魔羯座"
}
}
func ageFromBirthday(s string) string {
if len(s) < 4 {
return ""
}
y, err := strconv.Atoi(s[0:4])
if err != nil || y <= 0 {
return ""
}
age := time.Now().Year() - y
if age < 0 {
age = 0
}
return strconv.Itoa(age)
} }

View File

@@ -0,0 +1,54 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha"
)
// ProcessIVYZ2MN6Request IVYZ2MN6 API处理方法
func ProcessIVYZ2MN6Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ2MN6Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"name": encryptedName,
"idCard": encryptedIDCard,
"authorized": paramsDto.Authorized,
}
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI1004", reqData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
respBytes, err := json.Marshal(respData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -0,0 +1,54 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha"
)
// ProcessIVYZ2MN7Request IVYZ2MN7 API处理方法
func ProcessIVYZ2MN7Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ2MN6Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"name": encryptedName,
"idCard": encryptedIDCard,
"authorized": paramsDto.Authorized,
}
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI035", reqData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
respBytes, err := json.Marshal(respData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -0,0 +1,56 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shujubao"
)
// ProcessIVYZ38SRRequest IVYZ38SR 婚姻状态核验(双人) API 处理方法(使用数据宝服务示例)
func ProcessIVYZ38SRRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ38SRReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
fixedData := map[string]interface{}{"msg": "请联系商务咨询"}
fixedRespBytes, err := json.Marshal(fixedData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return fixedRespBytes, nil
reqParams := map[string]interface{}{
"key": "",
"name": paramsDto.ManName,
"idcard": paramsDto.ManIDCard,
"woman_name": paramsDto.WomanName,
"woman_idcard": paramsDto.WomanIDCard,
}
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
apiPath := "/communication/personal/10148"
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
if err != nil {
if errors.Is(err, shujubao.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
if errors.Is(err, shujubao.ErrQueryEmpty) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
respBytes, err := json.Marshal(data)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -0,0 +1,64 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/muzi"
)
// ProcessIVYZ3P9MRequest IVYZ3P9M API处理方法 - 学历查询实时版
func ProcessIVYZ3P9MRequest_2(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ3P9MReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedName, err := deps.MuziService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedCertCode, err := deps.MuziService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
// 处理 returnType 参数,默认为 "1"
returnType := paramsDto.ReturnType
if returnType == "" {
returnType = "1"
}
paramSign := map[string]interface{}{
"returnType": returnType,
"realName": encryptedName,
"certCode": encryptedCertCode,
}
reqData := map[string]interface{}{
"realName": encryptedName,
"certCode": encryptedCertCode,
"returnType": returnType,
}
respData, err := deps.MuziService.CallAPI(ctx, "PC0041", "/academic", reqData, paramSign)
if err != nil {
switch {
case errors.Is(err, muzi.ErrDatasource):
return nil, errors.Join(processors.ErrDatasource, err)
case errors.Is(err, muzi.ErrSystem):
return nil, errors.Join(processors.ErrSystem, err)
default:
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respData, nil
}

View File

@@ -4,10 +4,11 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"strings"
"tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors" "tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/muzi" "tyapi-server/internal/infrastructure/external/zhicha"
) )
// ProcessIVYZ3P9MRequest IVYZ3P9M API处理方法 - 学历查询实时版 // ProcessIVYZ3P9MRequest IVYZ3P9M API处理方法 - 学历查询实时版
@@ -21,45 +22,147 @@ func ProcessIVYZ3P9MRequest(ctx context.Context, params []byte, deps *processors
return nil, errors.Join(processors.ErrInvalidParam, err) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
encryptedName, err := deps.MuziService.Encrypt(paramsDto.Name) encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil { if err != nil {
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)
} }
encryptedCertCode, err := deps.MuziService.Encrypt(paramsDto.IDCard) encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
if err != nil { if err != nil {
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)
} }
// 处理 returnType 参数,默认为 "1"
returnType := paramsDto.ReturnType
if returnType == "" {
returnType = "1"
}
paramSign := map[string]interface{}{
"returnType": returnType,
"realName": encryptedName,
"certCode": encryptedCertCode,
}
reqData := map[string]interface{}{ reqData := map[string]interface{}{
"realName": encryptedName, "name": encryptedName,
"certCode": encryptedCertCode, "idCard": encryptedIDCard,
"returnType": returnType, "authorized": "1",
} }
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI1004", reqData)
respData, err := deps.MuziService.CallAPI(ctx, "PC0041", "/academic",reqData,paramSign)
if err != nil { if err != nil {
switch { if errors.Is(err, zhicha.ErrDatasource) {
case errors.Is(err, muzi.ErrDatasource):
return nil, errors.Join(processors.ErrDatasource, err) return nil, errors.Join(processors.ErrDatasource, err)
case errors.Is(err, muzi.ErrSystem): }
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)
default: }
return nil, errors.Join(processors.ErrSystem, err)
out, err := mapZCI1004ToIVYZ3P9M(respData, paramsDto.Name, paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return json.Marshal(out)
}
type zci1004Item struct {
EndDate string `json:"endDate"`
EducationLevel string `json:"educationLevel"`
LearningForm string `json:"learningForm"`
}
type ivyz3p9mItem struct {
GraduationDate string `json:"graduationDate"`
StudentName string `json:"studentName"`
EducationLevel string `json:"educationLevel"`
LearningForm string `json:"learningForm"`
IDNumber string `json:"idNumber"`
}
func mapZCI1004ToIVYZ3P9M(respData interface{}, name, idCard string) ([]ivyz3p9mItem, error) {
respBytes, err := json.Marshal(respData)
if err != nil {
return nil, err
}
var source []zci1004Item
if err := json.Unmarshal(respBytes, &source); err != nil {
var wrapped struct {
Data []zci1004Item `json:"data"`
}
if err2 := json.Unmarshal(respBytes, &wrapped); err2 != nil {
return nil, err
}
source = wrapped.Data
}
out := make([]ivyz3p9mItem, 0, len(source))
for _, it := range source {
out = append(out, ivyz3p9mItem{
GraduationDate: normalizeDateDigits(it.EndDate),
StudentName: name,
EducationLevel: mapEducationLevelToCode(it.EducationLevel),
LearningForm: mapLearningFormToCode(it.LearningForm),
IDNumber: idCard,
})
}
return out, nil
}
func mapEducationLevelToCode(level string) string {
v := normalizeText(level)
switch {
case strings.Contains(v, "第二学士"):
return "5"
case strings.Contains(v, "博士"):
return "4"
case strings.Contains(v, "硕士"):
return "3"
case strings.Contains(v, "本科"):
return "2"
case strings.Contains(v, "专科"), strings.Contains(v, "大专"):
return "1"
default:
return "99"
}
}
func mapLearningFormToCode(form string) string {
v := normalizeText(form)
switch {
case strings.Contains(v, "脱产"):
return "1"
case strings.Contains(v, "普通全日制"):
return "2"
case strings.Contains(v, "全日制"):
return "3"
case strings.Contains(v, "开放教育"), strings.Contains(v, "开放大学"):
return "4"
case strings.Contains(v, "夜大学"), strings.Contains(v, "夜大"):
return "5"
case strings.Contains(v, "函授"):
return "6"
case strings.Contains(v, "网络教育"), strings.Contains(v, "网教"), strings.Contains(v, "远程教育"):
return "7"
case strings.Contains(v, "非全日制"):
return "8"
case strings.Contains(v, "业余"):
return "9"
case strings.Contains(v, "自学考试"), strings.Contains(v, "自考"):
// 自考在既有枚举中无直对应,兼容并入“业余”
return "9"
default:
return "99"
}
}
func normalizeDateDigits(s string) string {
trimmed := strings.TrimSpace(s)
if trimmed == "" {
return ""
}
var b strings.Builder
for _, ch := range trimmed {
if ch >= '0' && ch <= '9' {
b.WriteRune(ch)
} }
} }
return b.String()
return respData, nil }
func normalizeText(s string) string {
v := strings.TrimSpace(strings.ToLower(s))
v = strings.ReplaceAll(v, " ", "")
v = strings.ReplaceAll(v, "-", "")
v = strings.ReplaceAll(v, "_", "")
return v
} }

View File

@@ -0,0 +1,58 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shujubao"
)
// ProcessIVYZ48SRRequest IVYZ48SR 婚姻状态核验V2双人 API 处理方法(使用数据宝服务示例)
func ProcessIVYZ48SRRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ48SRReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
fixedData := map[string]interface{}{"msg": "请联系商务咨询"}
fixedRespBytes, err := json.Marshal(fixedData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return fixedRespBytes, nil
reqParams := map[string]interface{}{
"key": "",
"name": paramsDto.ManName,
"idcard": paramsDto.ManIDCard,
"woman_name": paramsDto.WomanName,
"woman_idcard": paramsDto.WomanIDCard,
"marital_type": paramsDto.MaritalType,
"auth_authorize_file_code": paramsDto.AuthAuthorizeFileCode,
}
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
apiPath := "/communication/personal/10332"
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
if err != nil {
if errors.Is(err, shujubao.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
if errors.Is(err, shujubao.ErrQueryEmpty) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
respBytes, err := json.Marshal(data)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -0,0 +1,69 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha"
)
// ProcessIVYZ4Y27Request IVYZ4Y27 API处理方法 - 教育背景(详细)查询
func ProcessIVYZ4Y27Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ4Y27Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"name": encryptedName,
"idCard": encryptedIDCard,
"authorized": "1",
}
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI1004", reqData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
out, err := mapZCI1004ToIVYZ4Y27(respData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
respBytes, err := json.Marshal(out)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
// if deps.HuiboService == nil {
// return nil, errors.Join(processors.ErrSystem, errors.New("汇博服务未初始化"))
// }
// respBytes, err := deps.HuiboService.CallEducationBackgroundDetailed(ctx, paramsDto.Name, paramsDto.IDCard, paramsDto.AuthAuthorizeFileBase64)
// if err != nil {
// return nil, errors.Join(processors.ErrDatasource, err)
// }
// return respBytes, nil
}

View File

@@ -0,0 +1,83 @@
package ivyz
import (
"encoding/json"
"strings"
)
type ivyz4y27AbilityInfo struct {
AbilityStartDate string `json:"abilityStartDate"`
AbilityEndDate string `json:"abilityEndDate"`
AbilityType string `json:"abilityType"`
AbilityName string `json:"abilityName"`
AbilityCompetitiveDegree string `json:"abilityCompetitiveDegree"`
AbilityCompetitive string `json:"abilityCompetitive"`
AbilityField string `json:"abilityField"`
}
type ivyz4y27Response struct {
AbilityInfo []ivyz4y27AbilityInfo `json:"abilityInfo"`
}
func mapZCI1004ToIVYZ4Y27(respData interface{}) (*ivyz4y27Response, error) {
respBytes, err := json.Marshal(respData)
if err != nil {
return nil, err
}
var source []zci1004Item
if err := json.Unmarshal(respBytes, &source); err != nil {
var wrapped struct {
Data []zci1004Item `json:"data"`
}
if err2 := json.Unmarshal(respBytes, &wrapped); err2 != nil {
return nil, err
}
source = wrapped.Data
}
out := &ivyz4y27Response{
AbilityInfo: make([]ivyz4y27AbilityInfo, 0, len(source)),
}
for _, it := range source {
out.AbilityInfo = append(out.AbilityInfo, ivyz4y27AbilityInfo{
AbilityStartDate: "",
AbilityEndDate: formatDateToYYYYMMDD(it.EndDate),
AbilityType: strings.TrimSpace(it.LearningForm),
AbilityName: "",
AbilityCompetitiveDegree: mapEducationLevelToAbilityCompetitiveDegree(it.EducationLevel),
AbilityCompetitive: "",
AbilityField: "",
})
}
return out, nil
}
func mapEducationLevelToAbilityCompetitiveDegree(level string) string {
v := normalizeText(level)
switch {
case strings.Contains(v, "博士"):
return "9"
case strings.Contains(v, "硕士"):
return "8"
case strings.Contains(v, "本科"), strings.Contains(v, "第二学士"):
return "7"
case strings.Contains(v, "专科"), strings.Contains(v, "大专"):
return "6"
default:
return "5"
}
}
func formatDateToYYYYMMDD(s string) string {
digits := normalizeDateDigits(s)
switch len(digits) {
case 8:
return digits[:4] + "-" + digits[4:6] + "-" + digits[6:8]
case 6:
return digits[:4] + "-" + digits[4:6] + "-01"
default:
return ""
}
}

View File

@@ -0,0 +1,66 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha"
)
// ProcessIVYZ5E22Request API处理方法 - 双人婚姻评估查询
func ProcessIVYZ5E22Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ5E22Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedManName, err := deps.ZhichaService.Encrypt(paramsDto.ManName)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedManIDCard, err := deps.ZhichaService.Encrypt(paramsDto.ManIDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedWomanName, err := deps.ZhichaService.Encrypt(paramsDto.WomanName)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedWomanIDCard, err := deps.ZhichaService.Encrypt(paramsDto.WomanIDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"nameMan": encryptedManName,
"idCardMan": encryptedManIDCard,
"nameWoman": encryptedWomanName,
"idCardWoman": encryptedWomanIDCard,
"authorized": paramsDto.Authorized,
}
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI042", reqData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
} else {
return nil, errors.Join(processors.ErrSystem, err)
}
}
// 将响应数据转换为JSON字节
respBytes, err := json.Marshal(respData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

Some files were not shown because too many files have changed in this diff Show More