Compare commits
646 Commits
v5.13.0.sp
...
v5.13.0.sp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73c52bbd1f | ||
|
|
63773c97a5 | ||
|
|
6b75fe67af | ||
|
|
65aa023c92 | ||
|
|
7f77715cc0 | ||
|
|
c146e0e101 | ||
|
|
11478921a6 | ||
|
|
c5bd0bc597 | ||
|
|
b797bb99d0 | ||
|
|
fc37b649b1 | ||
|
|
fd3a334e32 | ||
|
|
f48a2b2aa8 | ||
|
|
dd7a1bd539 | ||
|
|
0f2c22dbd9 | ||
|
|
b6fff119e0 | ||
|
|
1d3b3a5f9d | ||
|
|
7e6426adea | ||
|
|
daae9f05a6 | ||
|
|
615448c2ce | ||
|
|
5bf6c026f0 | ||
|
|
c887260f40 | ||
|
|
bc05da3bd2 | ||
|
|
3585737d21 | ||
|
|
da999aa4e6 | ||
|
|
8d62eb25b2 | ||
|
|
3d06b8d009 | ||
|
|
1c5e49b081 | ||
|
|
b522b3ff1e | ||
|
|
743dcc6f4c | ||
|
|
7de53c00ef | ||
|
|
c0f02b4599 | ||
|
|
278f67fe72 | ||
|
|
d41545f2b0 | ||
|
|
bbbace28ea | ||
|
|
27447c2479 | ||
|
|
3107ab8f3a | ||
|
|
63dd5c161d | ||
|
|
511d76e03f | ||
|
|
a683a9ed5c | ||
|
|
e64b1d3b49 | ||
|
|
3b7ef10492 | ||
|
|
b17c58e792 | ||
|
|
6faa39ebea | ||
|
|
32f4d7e0ae | ||
|
|
fbf25775b3 | ||
|
|
0732be618e | ||
|
|
3e16c27442 | ||
|
|
2dea278a45 | ||
|
|
02adbd3af3 | ||
|
|
f1281b73cc | ||
|
|
5fc096ab0a | ||
|
|
28293383dd | ||
|
|
7459bc220c | ||
|
|
a81297c2be | ||
|
|
cb5957ee61 | ||
|
|
b55467312f | ||
|
|
a567c82ef2 | ||
|
|
2f6530e725 | ||
|
|
e1d8548e82 | ||
|
|
3c295c86b8 | ||
|
|
9118907838 | ||
|
|
1b465578b6 | ||
|
|
d78b7f3c17 | ||
|
|
4726020b54 | ||
|
|
eaa25b7975 | ||
|
|
01690c76cd | ||
|
|
6f05485e8f | ||
|
|
4e5d776104 | ||
|
|
b0e6664909 | ||
|
|
25574c1a54 | ||
|
|
b18dcb3001 | ||
|
|
228334faf2 | ||
|
|
fc3efc6819 | ||
|
|
cfca3406a1 | ||
|
|
a867020070 | ||
|
|
f063347492 | ||
|
|
8c5cefb626 | ||
|
|
ca54e4cd3b | ||
|
|
687a02b199 | ||
|
|
d4f2c1ddb7 | ||
|
|
375fbbe375 | ||
|
|
1b21375e1c | ||
|
|
723a088eed | ||
|
|
7bd5c9eed0 | ||
|
|
bc15521b3a | ||
|
|
c3e2f6fabb | ||
|
|
a1b5a868ec | ||
|
|
1794d086b4 | ||
|
|
8789c83942 | ||
|
|
681f4e7510 | ||
|
|
d30b5f60d7 | ||
|
|
179ec878ea | ||
|
|
95ab40a14f | ||
|
|
31eab113a2 | ||
|
|
6b4a69453d | ||
|
|
349757216e | ||
|
|
cc31d01f57 | ||
|
|
9c78772e1f | ||
|
|
9eb21e0d80 | ||
|
|
1f9af0706d | ||
|
|
885a69f9c8 | ||
|
|
91ded3eac6 | ||
|
|
864e996f64 | ||
|
|
7358034209 | ||
|
|
ad93ac861d | ||
|
|
70dec92cf8 | ||
|
|
e4d695ef93 | ||
|
|
0b7d490a8a | ||
|
|
75da09424a | ||
|
|
d877b8d93b | ||
|
|
d03ed31483 | ||
|
|
4c5c0e63d5 | ||
|
|
7c65d67b3e | ||
|
|
ea1744afda | ||
|
|
5ad7114d89 | ||
|
|
1b8b6162f4 | ||
|
|
695762b34c | ||
|
|
8f2444fdbb | ||
|
|
47b3d918eb | ||
|
|
79ab80062d | ||
|
|
f4c5910758 | ||
|
|
661fea7d29 | ||
|
|
b8a6f215e6 | ||
|
|
77b38b31d9 | ||
|
|
5580deb796 | ||
|
|
70bb0c1d57 | ||
|
|
731599ac70 | ||
|
|
adae0af8d6 | ||
|
|
00a260028f | ||
|
|
3e0a5abcda | ||
|
|
228b507f12 | ||
|
|
e69cc355b3 | ||
|
|
9684e75697 | ||
|
|
48e639f5d4 | ||
|
|
c0e40dfab0 | ||
|
|
1f7ddcf1cc | ||
|
|
cac28a3d57 | ||
|
|
282050262c | ||
|
|
b2545d4ae2 | ||
|
|
7a2850c3ff | ||
|
|
6715af11da | ||
|
|
c0637ebf0d | ||
|
|
9bc8d5dd55 | ||
|
|
38a56ab404 | ||
|
|
029f6906e4 | ||
|
|
d2b790702b | ||
|
|
f1a3dab555 | ||
|
|
cf7f27d3b0 | ||
|
|
2905763862 | ||
|
|
0e0ee1d7d3 | ||
|
|
5b6d7de04a | ||
|
|
cd5af0ab78 | ||
|
|
2931cc52bb | ||
|
|
52c62be658 | ||
|
|
6324fa15ae | ||
|
|
c7cc1343c8 | ||
|
|
11524982e5 | ||
|
|
54656cb441 | ||
|
|
05fc17bd5e | ||
|
|
97eb30fc11 | ||
|
|
3fda79ec8d | ||
|
|
a08a4cf06c | ||
|
|
0264dec97b | ||
|
|
500cbf8268 | ||
|
|
82732c2993 | ||
|
|
69a1941be6 | ||
|
|
80ffeac471 | ||
|
|
8826dc3259 | ||
|
|
b87fb6a07f | ||
|
|
e9ba5b96f2 | ||
|
|
0ee7337789 | ||
|
|
1356893d4e | ||
|
|
080b48559f | ||
|
|
af61f14a9a | ||
|
|
b53e257085 | ||
|
|
988eac224d | ||
|
|
186d18c160 | ||
|
|
132f814c2d | ||
|
|
f8736420de | ||
|
|
6dca1ded69 | ||
|
|
0b3b0731ff | ||
|
|
5bcc66d3f7 | ||
|
|
c14f464c3f | ||
|
|
75e6ccf888 | ||
|
|
3bf68070fe | ||
|
|
0374499e8a | ||
|
|
0b6a1157cc | ||
|
|
d76f991a9f | ||
|
|
00ec4d17c9 | ||
|
|
de33f20797 | ||
|
|
5f56575df4 | ||
|
|
4cade5fa31 | ||
|
|
de2c13279b | ||
|
|
87116f0fc7 | ||
|
|
3e68536eea | ||
|
|
6f55139127 | ||
|
|
4407d979e6 | ||
|
|
ddac21b170 | ||
|
|
94c5ad6e06 | ||
|
|
d429dde8c5 | ||
|
|
9c5db77a65 | ||
|
|
dddad4d113 | ||
|
|
0ff7e3bf8f | ||
|
|
bfc9953b32 | ||
|
|
4a3604169c | ||
|
|
8630ec6e71 | ||
|
|
f063d6327b | ||
|
|
8b97b011f8 | ||
|
|
04cbc2a4fe | ||
|
|
46fd24c819 | ||
|
|
46bdef5058 | ||
|
|
1fd1b3310f | ||
|
|
cc9add884d | ||
|
|
b59d91ea4e | ||
|
|
9e728c5608 | ||
|
|
05185bb29a | ||
|
|
35c6dabab3 | ||
|
|
4de0934e7f | ||
|
|
b12c010cd3 | ||
|
|
16ba930729 | ||
|
|
f717a5230a | ||
|
|
e47cc42da8 | ||
|
|
ca0fb1d2cb | ||
|
|
f027edab9a | ||
|
|
a578a70a9b | ||
|
|
9d2d00bc89 | ||
|
|
8a9739c362 | ||
|
|
ad25e79f31 | ||
|
|
b45cabbd25 | ||
|
|
e9bc20dd9b | ||
|
|
d18028a247 | ||
|
|
2ace98d3df | ||
|
|
99f908b338 | ||
|
|
05873ff46d | ||
|
|
a2e7f3f640 | ||
|
|
24c7d20531 | ||
|
|
812e3f2a28 | ||
|
|
2c59192482 | ||
|
|
f31810325a | ||
|
|
ac47ac128d | ||
|
|
71ebba0b8f | ||
|
|
58e3c0f6e1 | ||
|
|
6350b7d87c | ||
|
|
647c17c350 | ||
|
|
79c1213391 | ||
|
|
49967508f4 | ||
|
|
65b3cb19be | ||
|
|
fa1107737a | ||
|
|
f4d3b2016e | ||
|
|
719ad508d0 | ||
|
|
a43f65f4b8 | ||
|
|
542c3ff07b | ||
|
|
1dbe3ae0ee | ||
|
|
235966b4a8 | ||
|
|
b6e69ea0c3 | ||
|
|
4a233121c9 | ||
|
|
23b14b4517 | ||
|
|
27254d09b3 | ||
|
|
f2f75aceee | ||
|
|
bd8bd10ed5 | ||
|
|
6984e97cd2 | ||
|
|
ea2a3193ea | ||
|
|
290e8ec31e | ||
|
|
30039a35bb | ||
|
|
14f57be279 | ||
|
|
207897e7be | ||
|
|
df728bc3d6 | ||
|
|
3333ad2e10 | ||
|
|
83752a77cb | ||
|
|
14437dfb7a | ||
|
|
ce1c06eef2 | ||
|
|
a98f8a816a | ||
|
|
5d16bb8103 | ||
|
|
48dbec081d | ||
|
|
f9648bcaf8 | ||
|
|
68a7c395e0 | ||
|
|
0b4bb63dbe | ||
|
|
03c5d4a051 | ||
|
|
380ee54ae5 | ||
|
|
6e3b93f645 | ||
|
|
3eaa799455 | ||
|
|
283d27d0df | ||
|
|
8c86d1534c | ||
|
|
40054e4c6e | ||
|
|
2981699bd1 | ||
|
|
3484d93a42 | ||
|
|
9a70ddb758 | ||
|
|
805759db01 | ||
|
|
481246f52e | ||
|
|
ba077ecd41 | ||
|
|
00d3995bd4 | ||
|
|
ab801bd9cc | ||
|
|
d71ad5e140 | ||
|
|
e7177d7f65 | ||
|
|
8fba82935b | ||
|
|
7d9b643edf | ||
|
|
2f74e04645 | ||
|
|
1e723aae04 | ||
|
|
330a2977eb | ||
|
|
8167fff4bc | ||
|
|
6c3ecdac63 | ||
|
|
874a4465ea | ||
|
|
c3e07cd96c | ||
|
|
509ab88fee | ||
|
|
edce962f2c | ||
|
|
01f599db82 | ||
|
|
518b386637 | ||
|
|
586a6adf97 | ||
|
|
495da42f2b | ||
|
|
b353477793 | ||
|
|
4d3dd85a1c | ||
|
|
d7b4c2494d | ||
|
|
0abd8cbd5a | ||
|
|
9355576fad | ||
|
|
f979005e2f | ||
|
|
f65c6e0ab5 | ||
|
|
dbafe28918 | ||
|
|
ec47a71e16 | ||
|
|
4b566084e3 | ||
|
|
a4ecd765d4 | ||
|
|
f3ea863e76 | ||
|
|
7787d7ce0a | ||
|
|
3cfef3c57e | ||
|
|
8a10f1679a | ||
|
|
f80e1b6c3a | ||
|
|
49393124ac | ||
|
|
8e9b9043aa | ||
|
|
a210e7f621 | ||
|
|
9cf4cd30a2 | ||
|
|
60d6b9c1cf | ||
|
|
b499bb951b | ||
|
|
da91070c30 | ||
|
|
8fd7fcb489 | ||
|
|
cad2ff85ef | ||
|
|
8bbd343af4 | ||
|
|
7b4499135b | ||
|
|
da954bf68d | ||
|
|
6b428bcb50 | ||
|
|
1d94e4511d | ||
|
|
91b6344479 | ||
|
|
35a32ce799 | ||
|
|
768c162150 | ||
|
|
f6e598d225 | ||
|
|
5084ac39cf | ||
|
|
ea5e164a4e | ||
|
|
fcc53684cd | ||
|
|
c53b52f932 | ||
|
|
5f104fce87 | ||
|
|
a15fb508f9 | ||
|
|
89d785f04c | ||
|
|
09b1505120 | ||
|
|
6561862aae | ||
|
|
e4335eb840 | ||
|
|
5e5111523d | ||
|
|
ad893798a5 | ||
|
|
414f44c338 | ||
|
|
237ee91644 | ||
|
|
c0b42921ec | ||
|
|
3394ddcc03 | ||
|
|
bfd128f6bd | ||
|
|
1263b18724 | ||
|
|
e942c70553 | ||
|
|
3d63ccba2f | ||
|
|
7b20b3cc0e | ||
|
|
0caf88a0bc | ||
|
|
5e933119f3 | ||
|
|
b4f3e6b6a2 | ||
|
|
e4fc9057b3 | ||
|
|
dd3e52a8ca | ||
|
|
e81e548f92 | ||
|
|
616e2f45cb | ||
|
|
de312d3ec6 | ||
|
|
41456b8965 | ||
|
|
afe32c5abe | ||
|
|
31349430b6 | ||
|
|
11747540ad | ||
|
|
a8ad781197 | ||
|
|
a8353b654b | ||
|
|
66b4932d9d | ||
|
|
b50e6005da | ||
|
|
5567e96d65 | ||
|
|
865986da28 | ||
|
|
13d8b8390a | ||
|
|
cc0794c3fc | ||
|
|
0a29bed4ad | ||
|
|
5a4c03332a | ||
|
|
fa6b887ed2 | ||
|
|
d3da625f72 | ||
|
|
c70889eedd | ||
|
|
6c62d191c5 | ||
|
|
2900fb0ce2 | ||
|
|
7597dcd572 | ||
|
|
3430d160bf | ||
|
|
ae63df2d55 | ||
|
|
668042ca1a | ||
|
|
3eef280ade | ||
|
|
f83e3f5e7f | ||
|
|
92870be96e | ||
|
|
a3a7bd0230 | ||
|
|
d0eba614bd | ||
|
|
7f42e99070 | ||
|
|
867ba3933c | ||
|
|
149ca55246 | ||
|
|
15c180440f | ||
|
|
8053457e17 | ||
|
|
9f952f1b5b | ||
|
|
66740854f6 | ||
|
|
ecb31bf21a | ||
|
|
2e8cc49dfa | ||
|
|
97ef1fdec1 | ||
|
|
9decf2ed58 | ||
|
|
54b9d85b0d | ||
|
|
1d1eda1f0a | ||
|
|
65ed0fa550 | ||
|
|
15c440be0a | ||
|
|
dbc4eb9bed | ||
|
|
beb3285a51 | ||
|
|
1b74cbe4ba | ||
|
|
3fde0742aa | ||
|
|
71ea34ec0e | ||
|
|
41f1675c2b | ||
|
|
57deb501ba | ||
|
|
e806f67b9f | ||
|
|
dc80b74c79 | ||
|
|
a3b4a1fc7d | ||
|
|
660c045965 | ||
|
|
5c54c1aae4 | ||
|
|
132ff7a58d | ||
|
|
d26d86805a | ||
|
|
de601181df | ||
|
|
cdd95e1ad5 | ||
|
|
da33d8781b | ||
|
|
6529177a6b | ||
|
|
391e20d6f8 | ||
|
|
4042b8cace | ||
|
|
cde338b7c2 | ||
|
|
03f5b373f6 | ||
|
|
586ee75d4a | ||
|
|
9ef6140be0 | ||
|
|
56fbc4d52a | ||
|
|
2f315cd39a | ||
|
|
66e5bff947 | ||
|
|
21c8fe15b7 | ||
|
|
49506fc10e | ||
|
|
57d767226a | ||
|
|
0a9007793a | ||
|
|
c82b83492d | ||
|
|
6e3a1476ca | ||
|
|
7abd08f04e | ||
|
|
11168f4356 | ||
|
|
6d0d134ea2 | ||
|
|
ac17706fc7 | ||
|
|
0e82478fad | ||
|
|
f7344d4736 | ||
|
|
137080b3c0 | ||
|
|
2bf621c0cf | ||
|
|
59e0a80b37 | ||
|
|
77d4247449 | ||
|
|
04028745ad | ||
|
|
9b9e7a85a6 | ||
|
|
a0db2ed70a | ||
|
|
5dd59473a3 | ||
|
|
6bd9f8179a | ||
|
|
59e3745580 | ||
|
|
9164fe2dc6 | ||
|
|
70f26006da | ||
|
|
9332517f39 | ||
|
|
b53789818d | ||
|
|
cc0f5fedb8 | ||
|
|
7696692ad9 | ||
|
|
ddaaca6be8 | ||
|
|
96bd62a375 | ||
|
|
b9f6b93bbe | ||
|
|
4df4f822bc | ||
|
|
45b8595122 | ||
|
|
03a031360c | ||
|
|
c222ab29ff | ||
|
|
ff58e4f05c | ||
|
|
0e95a41edc | ||
|
|
680a3f2a8c | ||
|
|
aa904c5763 | ||
|
|
293821d0ab | ||
|
|
bcedc006e1 | ||
|
|
be9326d671 | ||
|
|
debc349810 | ||
|
|
40cab1f49e | ||
|
|
14365a6e76 | ||
|
|
6cf4bebebf | ||
|
|
7914b18e20 | ||
|
|
d0fd74fa51 | ||
|
|
000fadeb95 | ||
|
|
d7c9138a31 | ||
|
|
5e4608d486 | ||
|
|
9415356b20 | ||
|
|
73a93b2721 | ||
|
|
d93b0c4814 | ||
|
|
81154d7cfb | ||
|
|
21215850e4 | ||
|
|
1b4a7523f8 | ||
|
|
68291c61da | ||
|
|
be19b92e82 | ||
|
|
0b6f5e285a | ||
|
|
2f83793638 | ||
|
|
11cc857de7 | ||
|
|
fe076d7caf | ||
|
|
5db33072ac | ||
|
|
5813875d6e | ||
|
|
1c4ce3842a | ||
|
|
4f722ea1d7 | ||
|
|
05bdaad7aa | ||
|
|
af06033521 | ||
|
|
a54344950b | ||
|
|
392dc80617 | ||
|
|
a32f6b67f5 | ||
|
|
e343bd7ba7 | ||
|
|
0deacd54bb | ||
|
|
33df1fa37c | ||
|
|
faba69ca6d | ||
|
|
0b7eb4138e | ||
|
|
bdfba55a82 | ||
|
|
aea8eb2241 | ||
|
|
f8cf61e02f | ||
|
|
8a765082cf | ||
|
|
abb86ed422 | ||
|
|
de4dc8542b | ||
|
|
e3c1f4f860 | ||
|
|
f91b714349 | ||
|
|
31da0c637e | ||
|
|
93181c921d | ||
|
|
231b8308c6 | ||
|
|
1e0c073bb4 | ||
|
|
e250b23084 | ||
|
|
2d78175001 | ||
|
|
4f4bbac861 | ||
|
|
758c66e5a5 | ||
|
|
025007203c | ||
|
|
ac32d2f061 | ||
|
|
d1973adfba | ||
|
|
ae0745954d | ||
|
|
fbf952247d | ||
|
|
a07cb38dab | ||
|
|
d00f4b6d25 | ||
|
|
cecec315d4 | ||
|
|
95f7987d22 | ||
|
|
8d87b4142c | ||
|
|
e32c87d009 | ||
|
|
d931e0f03a | ||
|
|
2f80eadd26 | ||
|
|
601205144f | ||
|
|
fe5d61e4a6 | ||
|
|
934821e8d4 | ||
|
|
20d2cd7408 | ||
|
|
dc6f361891 | ||
|
|
de03b43367 | ||
|
|
5f208ab60f | ||
|
|
9888d2f901 | ||
|
|
137c523267 | ||
|
|
d4936ac085 | ||
|
|
bcbdb18e15 | ||
|
|
d828879568 | ||
|
|
bfd767f65b | ||
|
|
25a41bd105 | ||
|
|
1e6779a473 | ||
|
|
fcb42bfda9 | ||
|
|
128690bd2a | ||
|
|
51542c11ed | ||
|
|
98876b588e | ||
|
|
00487c463d | ||
|
|
66b4f61f18 | ||
|
|
8dbbf13520 | ||
|
|
80dcbc7bce | ||
|
|
cdebf586d5 | ||
|
|
396464feda | ||
|
|
3e3176fc63 | ||
|
|
810ad853e3 | ||
|
|
270afadccc | ||
|
|
4beb86c2ea | ||
|
|
7c22be7a54 | ||
|
|
37dd13061c | ||
|
|
0b4cadf163 | ||
|
|
7934e40586 | ||
|
|
4e1b7bf17a | ||
|
|
739a07f0da | ||
|
|
5b619ce6dc | ||
|
|
6f49a9b5eb | ||
|
|
4208223617 | ||
|
|
dff27dc659 | ||
|
|
81cb9dcbba | ||
|
|
7fa294c450 | ||
|
|
5db4986d91 | ||
|
|
d91feb0659 | ||
|
|
440bb920b7 | ||
|
|
95fb0a5764 | ||
|
|
cc5a1f3a0e | ||
|
|
e51e1c1cd7 | ||
|
|
9c8cfa3f53 | ||
|
|
7750fb14b9 | ||
|
|
2ffd3bb7bb | ||
|
|
3a1b6314a1 | ||
|
|
924e035074 | ||
|
|
b2654d3044 | ||
|
|
2cfe47fcd0 | ||
|
|
520dd4694b | ||
|
|
48eb91d65b | ||
|
|
23941a4fb9 | ||
|
|
42065195ec | ||
|
|
9ca999a90a | ||
|
|
a445e43fbb | ||
|
|
c47398295b | ||
|
|
539ec80a3b | ||
|
|
f0814f45d9 | ||
|
|
9032d2f98d | ||
|
|
e6ce5206a4 | ||
|
|
f63a2757b6 | ||
|
|
90ccacbb38 | ||
|
|
47a4bd31a7 | ||
|
|
84df35df1b | ||
|
|
4ee9342f2d | ||
|
|
f6d1d18781 | ||
|
|
df3af9d059 | ||
|
|
4b2bbff887 | ||
|
|
59bd70328a | ||
|
|
2070c33c34 | ||
|
|
99625753ec | ||
|
|
5ce8523702 | ||
|
|
a76371e508 | ||
|
|
7659fa0e7d | ||
|
|
d9ea710e9b | ||
|
|
3271bb3afc | ||
|
|
b371d685d4 | ||
|
|
f18682a3a9 | ||
|
|
cce67cf0ad | ||
|
|
b919e68dad | ||
|
|
ca0adaeeed | ||
|
|
9b228d7808 | ||
|
|
171194cb6b | ||
|
|
07e2fe4955 | ||
|
|
64d694f025 | ||
|
|
f558bf356d | ||
|
|
aff67ed8fb | ||
|
|
b3b5d53763 | ||
|
|
74f72fb2b8 | ||
|
|
8e81648bdd | ||
|
|
bc0a9f7f80 | ||
|
|
023743bbe9 | ||
|
|
4638656f5d |
@@ -1,23 +1,25 @@
|
||||
### 是什么问题、该问题是怎么引起的?
|
||||
|
||||
1.
|
||||
1. 这里替换为你的问题
|
||||
|
||||
### 重现步骤、期望结果、截图、代码
|
||||
|
||||
1.
|
||||
1. 这里替换为你的操作步骤,期望结果
|
||||
|
||||
```
|
||||
这里贴你的代码块
|
||||
```
|
||||
|
||||
### 实际结果、报错信息、截图
|
||||
|
||||
1.
|
||||
1. 这里替换为你的操作步骤的实际结果
|
||||
|
||||
```
|
||||
这里贴错误信息
|
||||
这里贴你的错误信息
|
||||
```
|
||||
|
||||
### 环境版本:
|
||||
|
||||
- JDK版本:1.8、11、17
|
||||
- JDK版本:1.8、11、17、21
|
||||
- 浏览器版本:Chrome xx、Firefox xx、IE xx
|
||||
- 平台版本:JeeSite 4.x.x、5.x.x(pom.xml里查看)
|
||||
|
||||
47
README.md
47
README.md
@@ -2,7 +2,7 @@
|
||||
<p align="center">
|
||||
<img alt="JeeSite" src="https://jeesite.com/assets/images/logo.png" width="120" height="120" style="margin-bottom: 10px;">
|
||||
</p>
|
||||
<h3 align="center" style="margin:30px 0 30px;font-weight:bold;font-size:30px;">快速开发平台 - Spring Boot</h3>
|
||||
<h3 align="center" style="margin:30px 0 30px;font-weight:bold;font-size:30px;">快速开发平台 - Spring Boot 3</h3>
|
||||
<p align="center">
|
||||
<a href="https://spring.io/projects/spring-boot" target="__blank"><img alt="SpringBoot-V3.5 or 2.7" src="https://img.shields.io/badge/SpringBoot-V3.5 or 2.7-blue.svg"></a>
|
||||
<a href="https://v3.cn.vuejs.org/" target="__blank"><img alt="TypeScript-Vue3" src="https://img.shields.io/badge/TypeScript-Vue3-green.svg"></a>
|
||||
@@ -70,6 +70,8 @@
|
||||
|
||||
* 2021 年终发布 Vue3 的前后分离版本,使得 JeeSite 拥有同一个后台服务 Web 来支撑分离版和全栈版两套前端技术栈。
|
||||
|
||||
* 对接常见 AI 大模型(OpenAPI、Ollama、DeepSeek等),支持检索增强生成 RAG 技术,实现企业知识库智能对话。
|
||||
|
||||
* 支持国产化软硬件环境,如国产芯片、操作系统、数据库、中间件、国密算法等。
|
||||
|
||||
## 核心优势
|
||||
@@ -95,29 +97,30 @@
|
||||
|
||||
## 技术选型
|
||||
|
||||
* 主框架:Spring Boot 2.7、Spring Framework 5.3、Apache Shiro 1.12、J2Cache
|
||||
* 持久层:Apache MyBatis 3.5、Hibernate Validator 6.2、Alibaba Druid 1.2
|
||||
* 视图层:Spring MVC 5.3、Beetl 3.10(替换JSP)、Bootstrap 3.3、AdminLTE 2.4
|
||||
* 前端组件:jQuery 3.7、jqGrid 4.7、layer 3.5、zTree 3.5、jQuery Validation
|
||||
* 分离前端版:Node.js、TypeScript、Vue3、Vite、Ant Design Vue、Vue Vben Admin
|
||||
* 工作流引擎:Flowable 6.6、符合 BPMN 规范、在线流程设计器、中国式流程、退回、撤回、自由流
|
||||
* Bootstrap 版 支持 IE10 及以上版本及其他所有现代浏览器,如:谷歌、火狐、国产浏览器 等
|
||||
* Vue3 版 支持现代浏览器,如:谷歌 Chrome 86+、火狐、国产浏览器 等
|
||||
* 技术选型(详细):<http://jeesite.com/docs/technology/>
|
||||
* JeeSite Vue 版本:<https://gitee.com/thinkgem/jeesite-vue>
|
||||
* Spring Boot 3.x 版本:<https://gitee.com/thinkgem/jeesite5/tree/v5.springboot3>
|
||||
* 主框架:Spring Boot 3.5、Spring Framework 6、Apache Shiro 2、J2Cache
|
||||
* 持久层:Apache MyBatis 3.5、Hibernate Validator 8、Alibaba Druid 1.2
|
||||
* 分离版:Node.js、TypeScript、Vue3、Vite、Ant Design Vue、Vue Vben Admin
|
||||
* 经典版:Beetl 3.10(HTML)、jQuery 3.7、Bootstrap 3.3、AdminLTE 2.4
|
||||
* 分离版:支持所有现代浏览器,如:谷歌 Chrome 86+、微软 Edge、火狐、国产浏览器 等
|
||||
* 经典版:支持 IE10 和以上版本,以及其他所有现代浏览器,如:谷歌、火狐、国产浏览器 等
|
||||
* 工作流引擎:Flowable 7.1、符合 BPMN 规范、在线流程设计器、中国式流程、退回、撤回、自由流
|
||||
* 技术选型(详细)已支持数据库:<http://jeesite.com/docs/technology/>
|
||||
* JeeSite Vue 前后分离版:<https://gitee.com/thinkgem/jeesite-vue>
|
||||
* Spring Boot 3.x 版本:<https://gitee.com/thinkgem/jeesite5/tree/v5.springboot3/>
|
||||
* Spring Boot 2.x 版本:<https://gitee.com/thinkgem/jeesite5/tree/v5.springboot2/>
|
||||
|
||||
## 更多介绍
|
||||
|
||||
* 目录结构:<https://jeesite.com/docs/catalog/>
|
||||
* 内置功能:<https://jeesite.com/docs/function/>
|
||||
* 架构特点:<https://jeesite.com/docs/feature/>
|
||||
* 内置功能:<https://jeesite.com/docs/function/>
|
||||
* 目录结构:<https://jeesite.com/docs/catalog/>
|
||||
* 参数配置:<https://jeesite.com/docs/config/>
|
||||
* 开发规范:<https://jeesite.com/docs/standard/>
|
||||
* 代码生成:<https://jeesite.com/docs/code-gen/>
|
||||
|
||||
## 生态系统
|
||||
|
||||
* AI + RAG + CMS 人工智能助手:<https://jeesite.com/docs/cms-ai>
|
||||
* 分布式微服务(Spring Cloud):<https://gitee.com/thinkgem/jeesite-cloud>
|
||||
* Flowable业务流程引擎(BPM):<http://jeesite.com/docs/bpm/>
|
||||
* 多站点内容管理模块(CMS):<https://jeesite.com/docs/cms/>
|
||||
@@ -139,11 +142,11 @@
|
||||
|
||||
### 本地运行
|
||||
|
||||
1. 环境准备:`JDK 1.8 or 11、17`、`Maven 3.6+`、使用 `MySQL 5.7 or 8.0` 数据库、[其它数据库](https://jeesite.com/docs/technology/#_8、已支持数据库)
|
||||
2. 下载源码:<https://gitee.com/thinkgem/jeesite5/repository/archive/v5.9.zip> 并解压
|
||||
3. 打开文件:`/web/src/main/resources/config/application.yml` 配置JDBC连接
|
||||
4. 执行脚本:`/web/bin/init-data.bat` 初始化数据库
|
||||
5. 执行脚本:`/web/bin/run-tomcat.bat` 启动服务即可
|
||||
1. 环境准备:`JDK 17+`、`Maven 3.8+`、使用 `MySQL 8.0+` 数据库、[其它数据库](https://jeesite.com/docs/technology/#_8、已支持数据库)
|
||||
2. 下载源码:<https://gitee.com/thinkgem/jeesite5/repository/archive/v5.springboot3.zip> 并解压
|
||||
3. 打开文件:`/web/src/main/resources/config/application.yml` 配置JDBC连接(建立一个新库)
|
||||
4. 执行脚本:`/web/bin/init-data.bat(sh)` 初始化数据库(自动往新库里创建表和初始数据)
|
||||
5. 执行脚本:`/web/bin/run-tomcat.bat(sh)` 启动服务即可
|
||||
6. 浏览器访问:<http://127.0.0.1:8980/js> 账号 system 密码 admin
|
||||
7. 部署常见问题:<https://jeesite.com/docs/faq/>
|
||||
8. 分离端安装:<https://jeesite.com/docs/vue-install-deploy/>
|
||||
@@ -151,9 +154,9 @@
|
||||
|
||||
### 快速运行
|
||||
|
||||
1. 环境准备:`JDK 1.8 or 11、17`、`Maven 3.6+`、无需准备数据库(使用内嵌 H2 DB、包含 Vue 和 全栈双版本)
|
||||
2. 下载源码:<https://gitee.com/thinkgem/jeesite5/repository/archive/v5.9.zip> 并解压
|
||||
3. 执行脚本:`/web-fast/bin/run-tomcat.bat` 启动服务即可(自动初始化库)
|
||||
1. 环境准备:`JDK 17+`、`Maven 3.8+`、无需准备数据库(使用内嵌 H2 DB、包含 Vue 和 全栈双版本)
|
||||
2. 下载源码:<https://gitee.com/thinkgem/jeesite5/repository/archive/v5.springboot3.zip> 并解压
|
||||
3. 执行脚本:`/web-fast/bin/run-tomcat.bat(sh)` 启动服务即可(无需手动建库,自动初始化数据库)
|
||||
4. Vue分离版本地址:<http://127.0.0.1:8980/vue/login>
|
||||
5. 全栈版本地址:<http://127.0.0.1:8980/a/login>
|
||||
6. 初始登录账号:超级管理员:system 密码:admin
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.jeesite</groupId>
|
||||
<artifactId>jeesite-parent</artifactId>
|
||||
<version>5.13.0-SNAPSHOT</version>
|
||||
<version>5.13.0.springboot3-SNAPSHOT</version>
|
||||
<relativePath>../parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
|
||||
<!-- Servlet Api -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -45,12 +45,12 @@
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Java serialization -->
|
||||
|
||||
<!-- Fury serialize -->
|
||||
<dependency>
|
||||
<groupId>de.ruedigermoeller</groupId>
|
||||
<artifactId>fst</artifactId>
|
||||
<version>${fst.version}</version>
|
||||
<groupId>org.apache.fury</groupId>
|
||||
<artifactId>fury-core</artifactId>
|
||||
<version>${fury.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson json -->
|
||||
@@ -87,13 +87,6 @@
|
||||
<version>${snakeyaml.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache HTTP -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Email -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
@@ -18,8 +18,8 @@ import java.util.UUID;
|
||||
*/
|
||||
public class IdGen {
|
||||
|
||||
private static SecureRandom random = new SecureRandom();
|
||||
private static IdWorker idWorker = new IdWorker(-1, -1);
|
||||
private static final SecureRandom random = new SecureRandom();
|
||||
private static final IdWorker idWorker = new IdWorker(-1, -1);
|
||||
|
||||
/**
|
||||
* 生成UUID, 中间无-分割.
|
||||
|
||||
@@ -7,6 +7,9 @@ package com.jeesite.common.io;
|
||||
import com.jeesite.common.codec.EncodeUtils;
|
||||
import com.jeesite.common.collect.ListUtils;
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import net.sf.jmimemagic.Magic;
|
||||
import net.sf.jmimemagic.MagicMatch;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
@@ -15,9 +18,6 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import javax.activation.MimetypesFileTypeMap;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
package com.jeesite.common.io;
|
||||
|
||||
import com.alibaba.fastjson.parser.ParserConfig;
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
import org.springframework.boot.env.OriginTrackedMapPropertySource;
|
||||
import org.springframework.boot.env.PropertiesPropertySourceLoader;
|
||||
@@ -39,12 +38,12 @@ public class PropertyLoader implements org.springframework.boot.env.PropertySour
|
||||
List<PropertySource<?>> propertySources = new ArrayList<>();
|
||||
if (!isLoadJeeSitePropertySource) {
|
||||
isLoadJeeSitePropertySource = true;
|
||||
try {
|
||||
// 默认开启 FastJSON 1.x 的,安全模式
|
||||
ParserConfig.getGlobalInstance().setSafeMode(true);
|
||||
} catch (Throwable ignored) {
|
||||
// 兼容 FastJSON 2.x 的调用,忽略异常
|
||||
}
|
||||
// try {
|
||||
// // 默认开启 FastJSON 1.x 的,安全模式
|
||||
// ParserConfig.getGlobalInstance().setSafeMode(true);
|
||||
// } catch (Throwable ignored) {
|
||||
// // 兼容 FastJSON 2.x 的调用,忽略异常
|
||||
// }
|
||||
Properties properties = PropertiesUtils.getInstance().getProperties();
|
||||
propertySources.add(new OriginTrackedMapPropertySource("jeesite", properties));
|
||||
} else {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
package com.jeesite.common.lang;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
*/
|
||||
package com.jeesite.common.lang;
|
||||
|
||||
import com.jeesite.common.io.PropertiesUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.nustaq.serialization.FSTConfiguration;
|
||||
import org.apache.fury.Fury;
|
||||
import org.apache.fury.ThreadSafeFury;
|
||||
import org.apache.fury.config.Language;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -25,13 +27,24 @@ import java.lang.reflect.InvocationTargetException;
|
||||
public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ObjectUtils.class);
|
||||
private static final boolean isJavaSerialize;
|
||||
|
||||
static {
|
||||
String[] ver = StringUtils.split(System.getProperty("java.version"), StringUtils.DOT);
|
||||
isJavaSerialize = ver.length > 0 && Integer.parseInt(ver[0]) > 1;
|
||||
|
||||
/**
|
||||
* 当前类的实例持有者(静态内部类,延迟加载,懒汉式,线程安全的单例模式)
|
||||
*/
|
||||
private static final class Static {
|
||||
private static final Boolean isJavaSerialize;
|
||||
private static final ThreadSafeFury fury;
|
||||
static {
|
||||
isJavaSerialize = PropertiesUtils.getInstance()
|
||||
.getPropertyToBoolean("isJavaSerialize", "false");
|
||||
org.apache.fury.logging.LoggerFactory.useSlf4jLogging(true);
|
||||
fury = Fury.builder().withLanguage(Language.JAVA)
|
||||
.withRefTracking(true)
|
||||
.requireClassRegistration(false)
|
||||
.buildThreadSafeFury();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 转换为 Double 类型
|
||||
*/
|
||||
@@ -186,10 +199,10 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
|
||||
*/
|
||||
public static byte[] serialize(Object object) {
|
||||
try {
|
||||
if (isJavaSerialize) {
|
||||
if (Static.isJavaSerialize) {
|
||||
return ObjectUtils.serializeJava(object);
|
||||
}else {
|
||||
return ObjectUtils.serializeFst(object);
|
||||
} else {
|
||||
return ObjectUtils.serializeFury(object);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("serialize: {}", e.getMessage());
|
||||
@@ -204,10 +217,10 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
|
||||
*/
|
||||
public static Object unserialize(byte[] bytes) {
|
||||
try {
|
||||
if (isJavaSerialize) {
|
||||
if (Static.isJavaSerialize) {
|
||||
return ObjectUtils.unserializeJava(bytes);
|
||||
}else {
|
||||
return ObjectUtils.unserializeFst(bytes);
|
||||
} else {
|
||||
return ObjectUtils.unserializeFury(bytes);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("unserialize: {}", e.getMessage());
|
||||
@@ -265,51 +278,88 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
private static ThreadLocal<FSTConfiguration> fstConfiguration =
|
||||
new NamedThreadLocal<FSTConfiguration>("FSTConfiguration") {
|
||||
@Override
|
||||
public FSTConfiguration initialValue() {
|
||||
return FSTConfiguration.createDefaultConfiguration();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* FST 序列化对象
|
||||
* 序列化对象
|
||||
* @param object
|
||||
* @return
|
||||
*/
|
||||
public static byte[] serializeFst(Object object) {
|
||||
public static byte[] serializeFury(Object object) {
|
||||
if (object == null){
|
||||
return null;
|
||||
}
|
||||
long beginTime = System.currentTimeMillis();
|
||||
byte[] bytes = fstConfiguration.get().asByteArray(object);
|
||||
byte[] bytes = Static.fury.serialize(object);
|
||||
long totalTime = System.currentTimeMillis() - beginTime;
|
||||
if (totalTime > 30000){
|
||||
logger.warn(object.getClass() + " fst serialize time: " + TimeUtils.formatTime(totalTime));
|
||||
logger.warn(object.getClass() + " serialize time: " + TimeUtils.formatTime(totalTime));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* FST 反序列化对象
|
||||
* 反序列化对象
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
public static Object unserializeFst(byte[] bytes) {
|
||||
public static Object unserializeFury(byte[] bytes) {
|
||||
if (bytes == null){
|
||||
return null;
|
||||
}
|
||||
long beginTime = System.currentTimeMillis();
|
||||
Object object = fstConfiguration.get().asObject(bytes);
|
||||
Object object = Static.fury.deserialize(bytes);
|
||||
long totalTime = System.currentTimeMillis() - beginTime;
|
||||
if (totalTime > 30000 && object != null){
|
||||
logger.warn(object.getClass() + " fst unserialize time: " + TimeUtils.formatTime(totalTime));
|
||||
logger.warn(object.getClass() + " unserialize time: " + TimeUtils.formatTime(totalTime));
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
// private static ThreadLocal<FSTConfiguration> fstConfiguration =
|
||||
// new NamedThreadLocal<FSTConfiguration>("FSTConfiguration") {
|
||||
// @Override
|
||||
// public FSTConfiguration initialValue() {
|
||||
// return FSTConfiguration.createDefaultConfiguration();
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// /**
|
||||
// * FST 序列化对象
|
||||
// * @param object
|
||||
// * @return
|
||||
// */
|
||||
// public static byte[] serializeFst(Object object) {
|
||||
// if (object == null){
|
||||
// return null;
|
||||
// }
|
||||
// long beginTime = System.currentTimeMillis();
|
||||
// byte[] bytes = fstConfiguration.get().asByteArray(object);
|
||||
// long totalTime = System.currentTimeMillis() - beginTime;
|
||||
// if (totalTime > 30000){
|
||||
// logger.warn(object.getClass() + " fst serialize time: " + TimeUtils.formatTime(totalTime));
|
||||
// }
|
||||
// return bytes;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * FST 反序列化对象
|
||||
// * @param bytes
|
||||
// * @return
|
||||
// */
|
||||
// public static Object unserializeFst(byte[] bytes) {
|
||||
// if (bytes == null){
|
||||
// return null;
|
||||
// }
|
||||
// long beginTime = System.currentTimeMillis();
|
||||
// Object object = fstConfiguration.get().asObject(bytes);
|
||||
// long totalTime = System.currentTimeMillis() - beginTime;
|
||||
// if (totalTime > 30000 && object != null){
|
||||
// logger.warn(object.getClass() + " fst unserialize time: " + TimeUtils.formatTime(totalTime));
|
||||
// }
|
||||
// return object;
|
||||
// }
|
||||
|
||||
// private static Pool<Kryo> kryoPool = new Pool<Kryo>(true, false, 8) {
|
||||
// protected Kryo create() {
|
||||
// Kryo kryo = new Kryo();
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serial;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -40,6 +41,7 @@ import java.util.TimeZone;
|
||||
*/
|
||||
public class JsonMapper extends ObjectMapper {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JsonMapper.class);
|
||||
@@ -78,7 +80,9 @@ public class JsonMapper extends ObjectMapper {
|
||||
this.setTimeZone(TimeZone.getTimeZone(PropertiesUtils.getInstance()
|
||||
.getProperty("lang.defaultTimeZone", "GMT+08:00")));
|
||||
this.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String[] pattern = new String[] {"yyyy", "MM", "dd", "HH", "mm", "ss", "SSS"};
|
||||
@Override
|
||||
public Object findSerializer(Annotated a) {
|
||||
if (a instanceof AnnotatedMethod) {
|
||||
@@ -120,10 +124,9 @@ public class JsonMapper extends ObjectMapper {
|
||||
return this;
|
||||
}
|
||||
|
||||
private static final String[] pattern = new String[] {"yyyy", "MM", "dd", "HH", "mm", "ss", "SSS"};
|
||||
private static final class JeeSiteJsonSerializer extends JsonSerializer<Date> {
|
||||
public static final class JeeSiteJsonSerializer extends JsonSerializer<Date> {
|
||||
private final String pattern;
|
||||
private JeeSiteJsonSerializer(String pattern) {
|
||||
public JeeSiteJsonSerializer(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
@Override
|
||||
@@ -137,9 +140,10 @@ public class JsonMapper extends ObjectMapper {
|
||||
}
|
||||
}
|
||||
}
|
||||
private static final class JeeSiteJsonDeserializer extends JsonDeserializer<Date> {
|
||||
|
||||
public static final class JeeSiteJsonDeserializer extends JsonDeserializer<Date> {
|
||||
private final String pattern;
|
||||
private JeeSiteJsonDeserializer(String pattern) {
|
||||
public JeeSiteJsonDeserializer(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
@Override
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serial;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
@@ -21,6 +22,7 @@ import java.util.TimeZone;
|
||||
*/
|
||||
public class XmlMapper extends com.fasterxml.jackson.dataformat.xml.XmlMapper{
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(XmlMapper.class);
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.jeesite.common.codec.EncodeUtils;
|
||||
import com.jeesite.common.io.PropertiesUtils;
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* IP 地址工具
|
||||
|
||||
@@ -6,11 +6,11 @@ package com.jeesite.common.utils;
|
||||
|
||||
import com.jeesite.common.io.PropertiesUtils;
|
||||
import com.jeesite.common.web.http.ServletUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.web.servlet.LocaleContextResolver;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
*/
|
||||
package com.jeesite.common.utils.excel;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* Excel Exception
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public class ExcelException extends RuntimeException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ExcelException() {
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.apache.poi.xssf.usermodel.XSSFRichTextString;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@@ -7,9 +7,9 @@ package com.jeesite.common.web;
|
||||
import com.jeesite.common.codec.EncodeUtils;
|
||||
import com.jeesite.common.io.PropertiesUtils;
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Cookie工具类
|
||||
|
||||
@@ -4,218 +4,262 @@
|
||||
*/
|
||||
package com.jeesite.common.web.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.CertificateException;
|
||||
import com.jeesite.common.codec.EncodeUtils;
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.io.InputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.TrustStrategy;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.ssl.SSLContextBuilder;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import com.jeesite.common.codec.EncodeUtils;
|
||||
|
||||
/**
|
||||
* HTTP客户端工具类(支持HTTPS)
|
||||
* HTTP 客户端工具类(支持HTTPS)
|
||||
* @author ThinkGem
|
||||
* @version 2017-3-27
|
||||
* @version 2025-03-26
|
||||
*/
|
||||
public class HttpClientUtils {
|
||||
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class);
|
||||
private static final HttpClient client = createHttpClient(60);
|
||||
|
||||
/**
|
||||
* http的get请求
|
||||
* @param url
|
||||
* HTTP 的 GET 请求
|
||||
*/
|
||||
public static String get(String url) {
|
||||
return get(url, EncodeUtils.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* http的get请求
|
||||
* @param url
|
||||
*/
|
||||
public static String get(String url, String charset) {
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
return executeRequest(httpGet, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* http的get请求,增加异步请求头参数
|
||||
* @param url
|
||||
*/
|
||||
public static String ajaxGet(String url) {
|
||||
return ajaxGet(url, EncodeUtils.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* http的get请求,增加异步请求头参数
|
||||
* @param url
|
||||
*/
|
||||
public static String ajaxGet(String url, String charset) {
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
httpGet.setHeader("X-Requested-With", "XMLHttpRequest");
|
||||
return executeRequest(httpGet, charset);
|
||||
return get(url, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* http的post请求,传递map格式参数
|
||||
* HTTP 的 GET 请求,传递 Map 格式参数
|
||||
*/
|
||||
public static String get(String url, Map<String, String> dataMap) {
|
||||
return get(url, dataMap, EncodeUtils.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP 的 GET 请求,传递 Map 格式参数,支持指定编码
|
||||
*/
|
||||
public static String get(String url, Map<String, String> dataMap, String charset) {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(buildUrl(url, dataMap, charset)))
|
||||
.GET()
|
||||
.build();
|
||||
return executeRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP 的 GET 请求,增加 ajax 请求头
|
||||
*/
|
||||
public static String ajaxGet(String url) {
|
||||
return ajaxGet(url, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP 的 GET 请求,增加 ajax 请求头,传递 Map 格式参数
|
||||
*/
|
||||
public static String ajaxGet(String url, Map<String, String> dataMap) {
|
||||
return ajaxGet(url, dataMap, EncodeUtils.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP 的 GET 请求,增加 ajax 请求头,传递 Map 格式参数,支持指定编码
|
||||
*/
|
||||
public static String ajaxGet(String url, Map<String, String> dataMap, String charset) {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(buildUrl(url, dataMap, charset)))
|
||||
.header("X-Requested-With", "XMLHttpRequest")
|
||||
.GET()
|
||||
.build();
|
||||
return executeRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建表单数据,Map 转换 params,支持指定编码
|
||||
*/
|
||||
private static String buildUrl(String url, Map<String, String> dataMap, String charset) {
|
||||
url = StringUtils.substringBefore(url, "#");
|
||||
if (dataMap == null) {
|
||||
return url;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(url);
|
||||
if (!url.contains("?")) {
|
||||
sb.append("?");
|
||||
} else if (!url.endsWith("&")) {
|
||||
sb.append("&");
|
||||
}
|
||||
return sb + buildFormData(dataMap, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP 的 POST 请求,传递 Map 格式参数
|
||||
*/
|
||||
public static String post(String url, Map<String, String> dataMap) {
|
||||
return post(url, dataMap, EncodeUtils.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* http的post请求,传递map格式参数
|
||||
* HTTP 的 POST 请求,传递 Map 格式参数,支持指定编码
|
||||
*/
|
||||
public static String post(String url, Map<String, String> dataMap, String charset) {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
try {
|
||||
if (dataMap != null){
|
||||
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
|
||||
for (Map.Entry<String, String> entry : dataMap.entrySet()) {
|
||||
nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nvps, charset);
|
||||
formEntity.setContentEncoding(charset);
|
||||
httpPost.setEntity(formEntity);
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return executeRequest(httpPost, charset);
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(buildFormData(dataMap, charset)))
|
||||
.build();
|
||||
return executeRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* http的post请求,增加异步请求头参数,传递map格式参数
|
||||
* HTTP 的 POST 请求,增加 ajax 请求头,传递 Map 格式参数
|
||||
*/
|
||||
public static String ajaxPost(String url, Map<String, String> dataMap) {
|
||||
return ajaxPost(url, dataMap, EncodeUtils.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* http的post请求,增加异步请求头参数,传递map格式参数
|
||||
* HTTP 的 POST 请求,增加 ajax 请求头,传递 Map 格式参数,支持指定编码
|
||||
*/
|
||||
public static String ajaxPost(String url, Map<String, String> dataMap, String charset) {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
httpPost.setHeader("X-Requested-With", "XMLHttpRequest");
|
||||
try {
|
||||
if (dataMap != null){
|
||||
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
|
||||
for (Map.Entry<String, String> entry : dataMap.entrySet()) {
|
||||
nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nvps, charset);
|
||||
formEntity.setContentEncoding(charset);
|
||||
httpPost.setEntity(formEntity);
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return executeRequest(httpPost, charset);
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("X-Requested-With", "XMLHttpRequest")
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(buildFormData(dataMap, charset)))
|
||||
.build();
|
||||
return executeRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* http的post请求,增加异步请求头参数,传递json格式参数
|
||||
* 构建表单数据,Map 转换 params,支持指定编码
|
||||
*/
|
||||
private static String buildFormData(Map<String, String> dataMap, String charset) {
|
||||
return dataMap.entrySet().stream()
|
||||
.map(entry -> entry.getKey() + "="
|
||||
+ EncodeUtils.encodeUrl(entry.getValue(), charset))
|
||||
.reduce((a, b) -> a + "&" + b)
|
||||
.orElse(StringUtils.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP 的 POST 请求,使用 json 请求头,传递 json 格式参数
|
||||
*/
|
||||
public static String ajaxPostJson(String url, String jsonString) {
|
||||
return ajaxPostJson(url, jsonString, EncodeUtils.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* http的post请求,增加异步请求头参数,传递json格式参数
|
||||
* HTTP 的 POST 请求,使用 json 请求头,传递 json 格式参数,支持指定编码
|
||||
*/
|
||||
public static String ajaxPostJson(String url, String jsonString, String charset) {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
httpPost.setHeader("X-Requested-With", "XMLHttpRequest");
|
||||
// try {
|
||||
StringEntity stringEntity = new StringEntity(jsonString, charset);// 解决中文乱码问题
|
||||
stringEntity.setContentEncoding(charset);
|
||||
stringEntity.setContentType("application/json");
|
||||
httpPost.setEntity(stringEntity);
|
||||
// } catch (UnsupportedEncodingException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
return executeRequest(httpPost, charset);
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("X-Requested-With", "XMLHttpRequest")
|
||||
.header("Content-Type", "application/json; charset="
|
||||
+ (StringUtils.isNotBlank(charset) ? charset : EncodeUtils.UTF_8))
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonString))
|
||||
.build();
|
||||
return executeRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一个http请求,传递HttpGet或HttpPost参数
|
||||
* 执行一个 http 请求,传递 HttpRequest 参数
|
||||
*/
|
||||
public static String executeRequest(HttpUriRequest httpRequest) {
|
||||
return executeRequest(httpRequest, EncodeUtils.UTF_8);
|
||||
public static String executeRequest(HttpRequest request) {
|
||||
try {
|
||||
return client.send(request, HttpResponse.BodyHandlers.ofString()).body();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一个http请求,传递HttpGet或HttpPost参数
|
||||
* HTTP 的 GET 请求,返回文件流
|
||||
*/
|
||||
public static String executeRequest(HttpUriRequest httpRequest, String charset) {
|
||||
CloseableHttpClient httpclient;
|
||||
if ("https".equals(httpRequest.getURI().getScheme())){
|
||||
httpclient = createSSLInsecureClient();
|
||||
}else{
|
||||
httpclient = HttpClients.createDefault();
|
||||
}
|
||||
String result = "";
|
||||
try {
|
||||
try {
|
||||
CloseableHttpResponse response = httpclient.execute(httpRequest);
|
||||
HttpEntity entity = null;
|
||||
try {
|
||||
entity = response.getEntity();
|
||||
result = EntityUtils.toString(entity, charset);
|
||||
} finally {
|
||||
EntityUtils.consume(entity);
|
||||
response.close();
|
||||
}
|
||||
} finally {
|
||||
httpclient.close();
|
||||
}
|
||||
}catch(IOException ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
public static InputStream getInputStream(String url) {
|
||||
return getInputStream(url, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建 SSL连接
|
||||
* HTTP 的 GET 请求,传递 Map 格式参数,返回文件流
|
||||
*/
|
||||
public static CloseableHttpClient createSSLInsecureClient() {
|
||||
public static InputStream getInputStream(String url, Map<String, String> dataMap) {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(buildUrl(url, dataMap, EncodeUtils.UTF_8)))
|
||||
.GET()
|
||||
.build();
|
||||
return executeRequestInputStream(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一个 http 请求,传递 HttpRequest 参数,返回文件流
|
||||
*/
|
||||
public static InputStream executeRequestInputStream(HttpRequest request) {
|
||||
try {
|
||||
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(new TrustStrategy() {
|
||||
@Override
|
||||
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
return true;
|
||||
}
|
||||
}).build();
|
||||
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
|
||||
} catch (GeneralSecurityException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||
if (response.statusCode() == 200) {
|
||||
return response.body();
|
||||
} else {
|
||||
logger.info("URL: {} statusCode: {}", request.uri().toString(), response.statusCode());
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpClient createHttpClient(long seconds) {
|
||||
HttpClient client;
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, new TrustManager[]{new UnsafeX509ExtendedTrustManager()}, new SecureRandom());
|
||||
client = HttpClient.newBuilder()
|
||||
.sslContext(sslContext)
|
||||
.connectTimeout(Duration.ofSeconds(seconds))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.info(e.getMessage(), e);
|
||||
client = HttpClient.newHttpClient();
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
private static final class UnsafeX509ExtendedTrustManager extends X509ExtendedTrustManager {
|
||||
|
||||
private static final X509Certificate[] EMPTY_CERTIFICATES = new X509Certificate[0];
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] certificates, String authType) {
|
||||
}
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] certificates, String authType, Socket socket) {
|
||||
}
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] certificates, String authType, SSLEngine sslEngine) {
|
||||
}
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] certificates, String authType) {
|
||||
}
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] certificates, String authType, Socket socket) {
|
||||
}
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] certificates, String authType, SSLEngine sslEngine) {
|
||||
}
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return EMPTY_CERTIFICATES;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
package com.jeesite.common.web.http;
|
||||
|
||||
import com.jeesite.common.io.PropertiesUtils;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 统一包装结果输出类:{ code: 200, msg: "", data: {} | [] }
|
||||
|
||||
@@ -12,15 +12,15 @@ import com.jeesite.common.lang.ExceptionUtils;
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
import com.jeesite.common.mapper.JsonMapper;
|
||||
import com.jeesite.common.mapper.XmlMapper;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
package com.jeesite.common.web.http;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import eu.bitwalker.useragentutils.Browser;
|
||||
import eu.bitwalker.useragentutils.DeviceType;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.test.web.http;
|
||||
|
||||
import com.jeesite.common.mapper.JsonMapper;
|
||||
import com.jeesite.common.web.http.HttpClientUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* HTTP客户端测试工具类(支持HTTPS)
|
||||
* @author ThinkGem
|
||||
* @version 2017-3-27
|
||||
*/
|
||||
public class HttpClientUtilsTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
String url = "https://vue.jeesite.com/js/a/sys/corpAdmin/treeData";
|
||||
Map<String, String> dataMap = new HashMap<>();
|
||||
dataMap.put("isShowCode", "true");
|
||||
dataMap.put("param1", "你好");
|
||||
System.out.println(HttpClientUtils.get(url, dataMap));
|
||||
System.out.println(HttpClientUtils.ajaxGet(url, dataMap));
|
||||
System.out.println(HttpClientUtils.post(url, dataMap));
|
||||
System.out.println(HttpClientUtils.ajaxPost(url, dataMap));
|
||||
System.out.println(HttpClientUtils.ajaxPostJson(url, JsonMapper.toJson(dataMap)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.jeesite</groupId>
|
||||
<artifactId>jeesite-parent</artifactId>
|
||||
<version>5.13.0-SNAPSHOT</version>
|
||||
<version>5.13.0.springboot3-SNAPSHOT</version>
|
||||
<relativePath>../../parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@ import com.jeesite.common.entity.DataEntity;
|
||||
import com.jeesite.common.mybatis.annotation.Column;
|
||||
import com.jeesite.common.mybatis.annotation.Table;
|
||||
import com.jeesite.common.mybatis.mapper.query.QueryType;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.io.Serial;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -36,6 +37,7 @@ import java.util.Date;
|
||||
)
|
||||
public class AppComment extends DataEntity<AppComment> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String category; // 问题分类
|
||||
private String content; // 问题和意见
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
*/
|
||||
package com.jeesite.modules.app.entity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.jeesite.common.entity.DataEntity;
|
||||
@@ -34,6 +35,7 @@ import com.jeesite.common.mybatis.mapper.query.QueryType;
|
||||
)
|
||||
public class AppUpgrade extends DataEntity<AppUpgrade> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String appCode; // 应用编号
|
||||
private String upTitle; // 升级标题
|
||||
|
||||
@@ -9,6 +9,8 @@ import com.jeesite.common.entity.Page;
|
||||
import com.jeesite.common.web.BaseController;
|
||||
import com.jeesite.modules.app.entity.AppComment;
|
||||
import com.jeesite.modules.app.service.AppCommentService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -20,8 +22,6 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
package com.jeesite.modules.app.web;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
</div>
|
||||
<% } %>
|
||||
<script>
|
||||
$("#inputForm").validate({
|
||||
$('#inputForm').validate({
|
||||
submitHandler: function(form){
|
||||
js.ajaxSubmitForm($(form), function(data){
|
||||
js.showMessage(data.message);
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
</div>
|
||||
<% } %>
|
||||
<script>
|
||||
$("#inputForm").validate({
|
||||
$('#inputForm').validate({
|
||||
submitHandler: function(form){
|
||||
js.ajaxSubmitForm($(form), function(data){
|
||||
js.showMessage(data.message);
|
||||
|
||||
183
modules/cms-ai/README.md
Normal file
183
modules/cms-ai/README.md
Normal file
@@ -0,0 +1,183 @@
|
||||
|
||||
## 模块简介
|
||||
|
||||
本模块基于 Spring AI 和 JeeSite 内容管理系统(CMS)并结合了检索增强生成(Retrieval-Augmented Generation, RAG)技术
|
||||
和先进的人工智能算法(AI),打造了一个强大的企业级知识管理和智能对话平台。该模块专为企业设计,旨在通过高效的知识获取和精准的对话能力,
|
||||
提升企业的信息管理效率和员工的工作效能。
|
||||
|
||||
检索增强生成 RAG 技术使系统能够自动从海量的企业文档中检索最相关的信息,并将其融入到生成的回答中,确保每一次查询都
|
||||
能获得最新且准确的结果。这种检索与生成相结合的方式,不仅提高了信息检索的准确性,还增强了回答的上下文关联性,
|
||||
特别适合处理复杂的企业知识库。
|
||||
|
||||
此外该模块,支持在线大模型和本地部署的大模型,如:Ollama、DeepSeek、通义千问,理论上支持所有 OpenAPI 标准接口的 AI 提供商。
|
||||
并能无缝集成多种嵌入式 AI 模型的向量数据库,如 Chroma、PGVector、Elasticsearch、Milvus 等,实现高效的数据存储、检索及分析。
|
||||
无论是大规模数据集还是高度专业化的领域知识,JeeSite CMS + RAG + AI 都能提供定制化解决方案,满足企业多样化的业务需求和技术要求。
|
||||
企业可以轻松管理和访问复杂的信息资源,促进内部知识共享和创新,从而在竞争激烈的市场环境中保持领先地位。
|
||||
|
||||
优势:本模块结构清晰,代码简洁易懂,不管是正式项目、或是学习 AI 技术、都能轻松应对读懂源代码。
|
||||
|
||||
## 在线演示
|
||||
|
||||
* 地址:<https://vue.jeesite.com/cms/chat/index>
|
||||
|
||||
## 源码下载
|
||||
|
||||
* 后端:<https://gitee.com/thinkgem/jeesite5/tree/v5.springboot3/modules/cms-ai>
|
||||
* 前端:<https://gitee.com/thinkgem/jeesite-vue/tree/main/packages/cms>
|
||||
|
||||
## AI 模型配置
|
||||
|
||||
支持的 AI 模型列表:<https://docs.spring.io/spring-ai/reference/1.0/api/index.html>
|
||||
|
||||
* 线上模型:理论上支持所有 [OpenAPI](https://help.aliyun.com/zh/model-studio/developer-reference/use-qwen-by-calling-api) 标准接口的 AI 提供商。
|
||||
|
||||
* 本地模型:使用 [Ollama](https://ollama.com) 安装方法,本文不多赘述,网上有很多安装资料。
|
||||
|
||||
* 模型类型包括:聊天对话模型和嵌入式向量库模型,需注意 dimensions 维度参数,要和模型要求的匹配。
|
||||
|
||||
```yml
|
||||
# 向量库类型:openai、ollama
|
||||
spring.ai.model.chat: openai
|
||||
```
|
||||
|
||||
具体配置项详见 [jeesite-cms-ai.yml](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml) 文件,有注释。
|
||||
|
||||
## 向量数据库配置
|
||||
|
||||
支持的向量库列表:<https://docs.spring.io/spring-ai/reference/1.0/api/vectordbs.html>
|
||||
|
||||
* Chroma
|
||||
* PGVector
|
||||
* Elasticsearch
|
||||
* Milvus
|
||||
* ...
|
||||
|
||||
```yml
|
||||
# 向量库类型:chroma、pgvector、elasticsearch、milvus、指定 none 表示不使用向量库
|
||||
spring.ai.vectorstore.type: none
|
||||
```
|
||||
|
||||
具体配置项详见 [jeesite-cms-ai.yml](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml) 文件,有注释。
|
||||
|
||||
### 安装 Chroma
|
||||
|
||||
```sh
|
||||
docker run -d --name chroma -p 8000:8000 ghcr.io/chroma-core/chroma:1.0.0
|
||||
```
|
||||
|
||||
### 安装 PGVector
|
||||
|
||||
```sh
|
||||
docker run -d --name pgvector -p 5433:5432 -e POSTGRES_USER=postgres \
|
||||
-e POSTGRES_PASSWORD=postgres pgvector/pgvector:pg17
|
||||
```
|
||||
|
||||
* 进入容器
|
||||
|
||||
```sql
|
||||
docker exec -it pgvector psql -U postgres
|
||||
```
|
||||
|
||||
* 建库语句
|
||||
|
||||
```sql
|
||||
CREATE DATABASE "jeesite-ai";
|
||||
|
||||
-- 激活数据库
|
||||
\connect "jeesite-ai";
|
||||
|
||||
-- 建立数据表和索引
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
CREATE EXTENSION IF NOT EXISTS hstore;
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- 使用 all-minilm 模型时创建
|
||||
DROP TABLE IF EXISTS vector_store_384;
|
||||
CREATE TABLE IF NOT EXISTS vector_store_384 (
|
||||
id varchar(64) DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||||
content text,
|
||||
metadata json,
|
||||
embedding vector(384)
|
||||
);
|
||||
CREATE INDEX ON vector_store_384 USING HNSW (embedding vector_cosine_ops);
|
||||
|
||||
-- 使用 nomic-embed-text 模型时创建
|
||||
DROP TABLE IF EXISTS vector_store_786;
|
||||
CREATE TABLE IF NOT EXISTS vector_store_786 (
|
||||
id varchar(64) DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||||
content text,
|
||||
metadata json,
|
||||
embedding vector(768)
|
||||
);
|
||||
CREATE INDEX ON vector_store_786 USING HNSW (embedding vector_cosine_ops);
|
||||
|
||||
-- 使用 bge-m3 模型时创建
|
||||
DROP TABLE IF EXISTS vector_store_1024;
|
||||
CREATE TABLE IF NOT EXISTS vector_store_1024 (
|
||||
id varchar(64) DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||||
content text,
|
||||
metadata json,
|
||||
embedding vector(1024)
|
||||
);
|
||||
CREATE INDEX ON vector_store_1024 USING HNSW (embedding vector_cosine_ops);
|
||||
```
|
||||
|
||||
## 创建 AI 菜单
|
||||
|
||||
系统管理 -> 系统设置 -> 菜单管理 -> 新增
|
||||
|
||||
* 菜单名称:AI 助手
|
||||
* 菜单地址:/cms/chat/index
|
||||
|
||||
## 工具调用 Tool Calling
|
||||
|
||||
工具调用 Tool Calling(也称 Function Calling)是人工智能应用程序中的常见模式,允许模型与一组 API 或工具交互,从而增强其功能。
|
||||
|
||||
实例代码,详见 [CmsAiTools.java](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/tools/CmsAiTools.java) 让 AI 调用你的 java 实现你的业务联动。
|
||||
|
||||
## 支持结构化输出
|
||||
|
||||
对于依赖可靠解析输出值的下游应用程序来说,LLM 产生结构化输出的能力很重要。开发人员希望将 AI 模型的结果快速转换为数据类型,如 JSON、XML 或 Java 类,这些类可以传递给其他应用程序函数和方法。
|
||||
|
||||
pom.xml 中注释掉 `<artifactId>spring-ai-starter-model-openai</artifactId>`
|
||||
打开注释 `<artifactId>spring-ai-starter-model-ollama</artifactId>`
|
||||
启用 `Ollama` 本地模型,测试类:`AiChatServiceTest.java`,或测试地址:
|
||||
|
||||
* 文本格式输出
|
||||
- 源码位置:CmsAiChatService.chatText(message)
|
||||
- 访问地址:<http://127.0.0.1:8980/js/a/cms/chat/text?message=你好>
|
||||
- 输出结果:`你好!有什么问题或需要帮助的吗?`
|
||||
* JSON 类型输出
|
||||
- 源码位置:CmsAiChatService.chatJson(message)
|
||||
- 访问地址:<http://127.0.0.1:8980/js/a/cms/chat/json?message=张三>
|
||||
- 输出结果:`{"sex":"男","name":"张三","age":"17"}`
|
||||
* 结合 Tool Calling 结构化输出
|
||||
- 开启参数:`spring.ai.tool-calls: true`
|
||||
- 源码位置:CmsAiChatService.chatJson(message)
|
||||
- 访问地址:<http://127.0.0.1:8980/js/a/cms/chat/json?message=打开客厅的灯>
|
||||
- 输出结果:`{"message":"客厅房间里的灯被打开","roomName":"客厅","on":true}`
|
||||
* Java 对象类型输出:
|
||||
- 源码位置:CmsAiChatService.chatArea(message)
|
||||
- 访问地址:<http://127.0.0.1:8980/js/a/cms/chat/entity?message=北京>
|
||||
- 输出结果:`[{"id":"110000","pageNo":1,"pageSize":10,"orderBy":"","isNewRecord":false,"dataMap":{},"status":"0","createBy":"system","createDate":"2025-01-01 19:25","updateBy":"system","updateDate":"2025-01-01 19:25","remarks":"","lastUpdateDateTime":1677843300000,"parentCodes":"0,","treeSort":110000,"treeSorts":"0000110000,","treeLeaf":"0","treeLevel":0,"treeNames":"北京市","childList":[{"id":"110100","pageNo":1,"pageSize":10,"orderBy":"","isNewRecord":false,"dataMap":{},"status":"0","createBy":"system","createDate":"2025-01-01 19:25","updateBy":"system","updateDate":"2025-01-01 19:25","remarks":"","lastUpdateDateTime":1677843300000,"parentCodes":"0,110000,","treeSort":110100,"treeSorts":"0000110000,0000110100,","treeLeaf":"0","treeLevel":1,"treeNames":"北京城区","childList":[{"id":"110101","isNewRecord":false,"areaCode":"110101","areaName":"东城区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110102","isNewRecord":false,"areaCode":"110102","areaName":"西城区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110105","isNewRecord":false,"areaCode":"110105","areaName":"朝阳区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110106","isNewRecord":false,"areaCode":"110106","areaName":"丰台区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110107","isNewRecord":false,"areaCode":"110107","areaName":"石景山区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110108","isNewRecord":false,"areaCode":"110108","areaName":"海淀区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110109","isNewRecord":false,"areaCode":"110109","areaName":"门头沟区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110111","isNewRecord":false,"areaCode":"110111","areaName":"房山区","areaType":"3","isRoot":true,"isTreeLeaf":false}],"isQueryChildren":true,"areaCode":"110100","areaName":"北京城区","areaType":"2","isRoot":false,"parentCode":"110000","isTreeLeaf":false,"parentName":"北京市"}],"isQueryChildren":true,"areaCode":"110000","areaName":"北京市","areaType":"1","isRoot":true,"isTreeLeaf":false}]`
|
||||
|
||||
## 授权协议声明
|
||||
|
||||
1. 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款。
|
||||
2. 不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
3. 在使用本软件时,由于它集成了众多第三方开源软件,请共同遵守这些开源软件的使用许可条款规定。
|
||||
4. 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议、版权声明和其他原作者
|
||||
规定需要包含的说明(请尊重原作者的著作权,不要删除或修改文件中的`Copyright`和`@author`信息)
|
||||
更不要,全局替换源代码中的 jeesite 或 ThinkGem 等字样,否则你将违反本协议条款承担责任。
|
||||
5. 您若套用本软件的一些代码或功能参考,请保留源文件中的版权和作者,需要在您的软件介绍明显位置
|
||||
说明出处,举例:本软件基于 JeeSite 快速开发平台,并附带链接:http://jeesite.com
|
||||
6. 任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
7. 如果你对本软件有改进,希望可以贡献给我们,共同进步。
|
||||
8. 本项目已申请软件著作权,请尊重开源,感谢阅读。
|
||||
9. 无用户数限制,无在线人数限制,放心使用。
|
||||
|
||||
## 技术支持与服务
|
||||
|
||||
* 本软件免费,我们也提供了相应的收费服务,因为:
|
||||
* 没有资金的支撑就很难得到发展,特别是一个好的产品,如果 JeeSite 帮助了您,请为我们点赞。支持我们,您可以获得更多回馈,我们会把公益事业做的更好,开放更多资源,回报社区和社会。请给我们一些动力吧,在此非常感谢已支持我们的朋友!
|
||||
* **联系我们**:请访问技术支持与服务页面:<http://s.jeesite.com>
|
||||
22
modules/cms-ai/bin/deploy.bat
Normal file
22
modules/cms-ai/bin/deploy.bat
Normal file
@@ -0,0 +1,22 @@
|
||||
@echo off
|
||||
rem /**
|
||||
rem * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
rem * No deletion without permission, or be held responsible to law.
|
||||
rem *
|
||||
rem * Author: ThinkGem@163.com
|
||||
rem */
|
||||
echo.
|
||||
echo [<5B><>Ϣ] <20><><EFBFBD>̵<F0B9A4B3>Maven<65><6E><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
echo.
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
|
||||
call mvn -v
|
||||
echo.
|
||||
|
||||
cd ..
|
||||
call mvn clean deploy -Dmaven.test.skip=true -Pdeploy
|
||||
|
||||
cd bin
|
||||
pause
|
||||
18
modules/cms-ai/bin/deploy.sh
Normal file
18
modules/cms-ai/bin/deploy.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
# /**
|
||||
# * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
# * No deletion without permission, or be held responsible to law.
|
||||
# *
|
||||
# * Author: ThinkGem@163.com
|
||||
# */
|
||||
echo ""
|
||||
echo "[信息] 部署工程到Maven服务器。"
|
||||
echo ""
|
||||
|
||||
mvn -v
|
||||
echo ""
|
||||
|
||||
cd ..
|
||||
mvn clean deploy -Dmaven.test.skip=true -Pdeploy
|
||||
|
||||
cd bin
|
||||
22
modules/cms-ai/bin/package.bat
Normal file
22
modules/cms-ai/bin/package.bat
Normal file
@@ -0,0 +1,22 @@
|
||||
@echo off
|
||||
rem /**
|
||||
rem * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
rem * No deletion without permission, or be held responsible to law.
|
||||
rem *
|
||||
rem * Author: ThinkGem@163.com
|
||||
rem */
|
||||
echo.
|
||||
echo [<5B><>Ϣ] <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ<EFBFBD><D7B0><EFBFBD>̣<EFBFBD><CCA3><EFBFBD><EFBFBD><EFBFBD>jar<61><72><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD>
|
||||
echo.
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
|
||||
call mvn -v
|
||||
echo.
|
||||
|
||||
cd ..
|
||||
call mvn clean install -Dmaven.test.skip=true -Ppackage
|
||||
|
||||
cd bin
|
||||
pause
|
||||
18
modules/cms-ai/bin/package.sh
Normal file
18
modules/cms-ai/bin/package.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
# /**
|
||||
# * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
# * No deletion without permission, or be held responsible to law.
|
||||
# *
|
||||
# * Author: ThinkGem@163.com
|
||||
# */
|
||||
echo ""
|
||||
echo "[信息] 打包安装工程,生成jar包文件。"
|
||||
echo ""
|
||||
|
||||
mvn -v
|
||||
echo ""
|
||||
|
||||
cd ..
|
||||
mvn clean install -Dmaven.test.skip=true -Ppackage
|
||||
|
||||
cd bin
|
||||
3590
modules/cms-ai/db/cms-ai.erm
Normal file
3590
modules/cms-ai/db/cms-ai.erm
Normal file
File diff suppressed because it is too large
Load Diff
150
modules/cms-ai/pom.xml
Normal file
150
modules/cms-ai/pom.xml
Normal file
@@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.jeesite</groupId>
|
||||
<artifactId>jeesite-parent</artifactId>
|
||||
<version>5.13.0.springboot3-SNAPSHOT</version>
|
||||
<relativePath>../../parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>jeesite-module-cms-ai</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>JeeSite Module CMS+RAG+AI</name>
|
||||
<url>http://jeesite.com</url>
|
||||
<inceptionYear>2013-Now</inceptionYear>
|
||||
|
||||
<properties>
|
||||
|
||||
<spring-ai.version>1.0.0</spring-ai.version>
|
||||
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jeesite</groupId>
|
||||
<artifactId>jeesite-module-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jeesite</groupId>
|
||||
<artifactId>jeesite-module-cms</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 在线大模型 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 本地大模型 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-ollama</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 向量数据库 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-advisors-vector-store</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Chroma 向量数据库 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-vector-store-chroma</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- PG 向量数据库 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ES 向量数据库 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-vector-store-elasticsearch</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Milvus 向量数据库 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-vector-store-milvus</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>slf4j-reload4j</artifactId>
|
||||
<groupId>org.slf4j</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-resolver-dns-native-macos</artifactId>
|
||||
<classifier>osx-aarch_64</classifier>
|
||||
</dependency>
|
||||
|
||||
<!-- HTML 转 Markdown -->
|
||||
<dependency>
|
||||
<groupId>com.vladsch.flexmark</groupId>
|
||||
<artifactId>flexmark-html2md-converter</artifactId>
|
||||
<version>0.64.8</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Office、zip 等文件内容解析 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-core</artifactId>
|
||||
<version>3.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-parsers-standard-package</artifactId>
|
||||
<version>3.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.27.1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bom</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>thinkgem</id>
|
||||
<name>WangZhen</name>
|
||||
<email>thinkgem at 163.com</email>
|
||||
<roles><role>Project lead</role></roles>
|
||||
<timezone>+8</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<organization>
|
||||
<name>JeeSite</name>
|
||||
<url>http://jeesite.com</url>
|
||||
</organization>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.cms.ai.config;
|
||||
|
||||
import com.jeesite.common.datasource.DataSourceHolder;
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
import com.jeesite.modules.cms.ai.properties.CmsAiProperties;
|
||||
import com.jeesite.modules.cms.ai.service.CacheChatMemoryRepository;
|
||||
import com.jeesite.modules.cms.ai.tools.CmsAiTools;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
/**
|
||||
* AI 聊天配置类
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(CmsAiProperties.class)
|
||||
public class CmsAiChatConfig {
|
||||
|
||||
/**
|
||||
* 聊天对话客户端
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Bean
|
||||
public ChatClient chatClient(ChatClient.Builder builder, CmsAiProperties properties) {
|
||||
if (StringUtils.isNotBlank(properties.getDefaultSystem())) {
|
||||
builder.defaultSystem(properties.getDefaultSystem());
|
||||
}
|
||||
if (properties.getToolCalls()) {
|
||||
builder.defaultTools(new CmsAiTools());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天对话数据存储
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Bean
|
||||
public ChatMemory chatMemory(CacheChatMemoryRepository cacheChatMemoryRepository) {
|
||||
return MessageWindowChatMemory.builder()
|
||||
.chatMemoryRepository(cacheChatMemoryRepository)
|
||||
.maxMessages(1024)
|
||||
.build();
|
||||
}
|
||||
|
||||
// @Bean
|
||||
// public BatchingStrategy batchingStrategy() {
|
||||
// return new TokenCountBatchingStrategy(EncodingType.CL100K_BASE, Integer.MAX_VALUE, 0.1);
|
||||
// }
|
||||
|
||||
/**
|
||||
* PG向量库数据源
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
@ConditionalOnProperty(name = "jdbc.ds_pgvector.type")
|
||||
public JdbcTemplate pgVectorStoreJdbcTemplate() {
|
||||
return DataSourceHolder.getRoutingDataSource()
|
||||
.getJdbcTemplate("ds_pgvector");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.cms.ai.config;
|
||||
|
||||
import com.jeesite.common.config.Global;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* MVC 异步任务池定义
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Configuration
|
||||
public class CmsAiWebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
|
||||
configurer.setTaskExecutor(webMvcAsyncTaskExecutor());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ThreadPoolTaskExecutor webMvcAsyncTaskExecutor() {
|
||||
ThreadPoolTaskExecutor bean = new ThreadPoolTaskExecutor();
|
||||
bean.setCorePoolSize(Global.getPropertyToInteger("web.taskPool.corePoolSize", "8"));
|
||||
bean.setMaxPoolSize(Global.getPropertyToInteger("web.taskPool.maxPoolSize", "20"));
|
||||
bean.setKeepAliveSeconds(Global.getPropertyToInteger("web.taskPool.keepAliveSeconds", "60"));
|
||||
bean.setQueueCapacity(Global.getPropertyToInteger("web.taskPool.queueCapacity", String.valueOf(Integer.MAX_VALUE)));
|
||||
bean.setThreadNamePrefix("web-async-");
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.cms.ai.config;
|
||||
|
||||
import com.jeesite.common.mapper.JsonMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* 推理模型OpenAI兼容处理
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Configuration
|
||||
public class WebClientThinkConfig {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(WebClientThinkConfig.class);
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebClientCustomizer webClientCustomizerThink() {
|
||||
return webClientBuilder -> {
|
||||
ExchangeFilterFunction requestFilter = ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
|
||||
logger.trace("Request url: {}: {}", clientRequest.method(), clientRequest.url());
|
||||
return Mono.just(clientRequest);
|
||||
});
|
||||
ExchangeFilterFunction responseFilter = ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
|
||||
logger.trace("Response status: {}", clientResponse.statusCode());
|
||||
AtomicBoolean thinkingFlag = new AtomicBoolean(false);
|
||||
Flux<DataBuffer> modifiedBody = clientResponse.bodyToFlux(DataBuffer.class)
|
||||
.map(buf -> {
|
||||
byte[] bytes = new byte[buf.readableByteCount()];
|
||||
buf.read(bytes);
|
||||
DataBufferUtils.release(buf);
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
})
|
||||
.flatMap(eventString -> {
|
||||
logger.trace("Original response: ==> {}", eventString);
|
||||
List<String> lines = new ArrayList<>();
|
||||
String[] list = eventString.split("\\n", -1);
|
||||
for (String line : list) {
|
||||
if (!line.startsWith("data: ")) {
|
||||
lines.add(line);
|
||||
continue;
|
||||
}
|
||||
String jsonPart = line.substring("data: ".length()).trim();
|
||||
if (!(StringUtils.startsWith(jsonPart, "{")
|
||||
&& StringUtils.endsWith(jsonPart, "}")
|
||||
&& !"data: [DONE]".equals(line))) {
|
||||
lines.add(line);
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> map = JsonMapper.fromJson(jsonPart, Map.class);
|
||||
if (map == null) {
|
||||
lines.add(line);
|
||||
continue;
|
||||
}
|
||||
// 修改内容字段
|
||||
List<Object> choices = (List<Object>)map.get("choices");
|
||||
if (choices == null) {
|
||||
lines.add(line);
|
||||
continue;
|
||||
}
|
||||
for (Object o : choices) {
|
||||
Map<String, Object> choice = (Map<String, Object>) o;
|
||||
if (choice == null) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> delta = (Map<String, Object>) choice.get("delta");
|
||||
if (delta == null) {
|
||||
continue;
|
||||
}
|
||||
String reasoningContent = (String) delta.get("reasoning_content");
|
||||
String content = (String) delta.get("content");
|
||||
if (StringUtils.isNotEmpty(reasoningContent) && StringUtils.isEmpty(content)) {
|
||||
if (!thinkingFlag.get()) {
|
||||
thinkingFlag.set(true);
|
||||
delta.put("content", "<think>\n" + reasoningContent);
|
||||
} else {
|
||||
delta.put("content", reasoningContent);
|
||||
}
|
||||
} else {
|
||||
if (thinkingFlag.get()) {
|
||||
thinkingFlag.set(false);
|
||||
delta.put("content", "</think>\n" + (content == null ? "" : content));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 重新生成事件字符串
|
||||
lines.add("data: " + JsonMapper.toJson(map));
|
||||
}
|
||||
String finalLine = StringUtils.join(lines, "\n");
|
||||
logger.trace("Modified response: ==> {}", finalLine);
|
||||
return Mono.just(finalLine);
|
||||
})
|
||||
.map(str -> {
|
||||
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
|
||||
return new DefaultDataBufferFactory().wrap(bytes);
|
||||
});
|
||||
ClientResponse modifiedResponse = ClientResponse.from(clientResponse)
|
||||
.headers(headers -> headers.remove(HttpHeaders.CONTENT_LENGTH))
|
||||
.body(modifiedBody)
|
||||
.build();
|
||||
return Mono.just(modifiedResponse);
|
||||
});
|
||||
webClientBuilder.filter(requestFilter).filter(responseFilter);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.jeesite.modules.cms.ai.properties;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
|
||||
@ConfigurationProperties("spring.ai")
|
||||
public class CmsAiProperties {
|
||||
|
||||
/**
|
||||
* 是否启用 Tool calling 工具调用
|
||||
*/
|
||||
private Boolean toolCalls = false;
|
||||
|
||||
/**
|
||||
* 默认系统提示词
|
||||
*/
|
||||
private String defaultSystem = "";
|
||||
|
||||
/**
|
||||
* 默认问题模板格式
|
||||
*/
|
||||
private String defaultPromptTemplate = "";
|
||||
|
||||
/**
|
||||
* 向量数据库设置
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private final Vectorstore vectorstore = new Vectorstore();
|
||||
|
||||
public Boolean getToolCalls() {
|
||||
return toolCalls;
|
||||
}
|
||||
|
||||
public void setToolCalls(Boolean toolCalls) {
|
||||
this.toolCalls = toolCalls;
|
||||
}
|
||||
|
||||
public String getDefaultSystem() {
|
||||
return defaultSystem;
|
||||
}
|
||||
|
||||
public void setDefaultSystem(String defaultSystem) {
|
||||
this.defaultSystem = defaultSystem;
|
||||
}
|
||||
|
||||
public String getDefaultPromptTemplate() {
|
||||
return defaultPromptTemplate;
|
||||
}
|
||||
|
||||
public void setDefaultPromptTemplate(String defaultPromptTemplate) {
|
||||
this.defaultPromptTemplate = defaultPromptTemplate;
|
||||
}
|
||||
|
||||
public Vectorstore getVectorstore() {
|
||||
return vectorstore;
|
||||
}
|
||||
|
||||
public static class Vectorstore {
|
||||
|
||||
/**
|
||||
* 向量库类型选择:chroma、pgvector、elasticsearch、milvus
|
||||
*/
|
||||
private String type;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.cms.ai.service;
|
||||
|
||||
import com.jeesite.common.collect.ListUtils;
|
||||
import com.jeesite.common.collect.MapUtils;
|
||||
import com.jeesite.common.config.Global;
|
||||
import com.jeesite.common.io.IOUtils;
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
import com.jeesite.common.lang.TimeUtils;
|
||||
import com.jeesite.common.utils.PageUtils;
|
||||
import com.jeesite.common.web.http.HttpClientUtils;
|
||||
import com.jeesite.common.web.http.ServletUtils;
|
||||
import com.jeesite.modules.cms.entity.Article;
|
||||
import com.jeesite.modules.cms.service.ArticleVectorStore;
|
||||
import com.jeesite.modules.cms.utils.CmsUtils;
|
||||
import com.vladsch.flexmark.html.renderer.LinkType;
|
||||
import com.vladsch.flexmark.html.renderer.ResolvedLink;
|
||||
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
|
||||
import com.vladsch.flexmark.html2md.converter.HtmlLinkResolver;
|
||||
import com.vladsch.flexmark.html2md.converter.HtmlLinkResolverFactory;
|
||||
import com.vladsch.flexmark.html2md.converter.HtmlNodeConverterContext;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.tika.Tika;
|
||||
import org.apache.tika.config.TikaConfig;
|
||||
import org.apache.tika.exception.TikaException;
|
||||
import org.apache.tika.io.TikaInputStream;
|
||||
import org.apache.tika.metadata.Metadata;
|
||||
import org.apache.tika.mime.MediaType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
|
||||
import org.springframework.ai.vectorstore.VectorStore;
|
||||
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* CMS 文章向量库存储
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Service
|
||||
public class ArticleVectorStoreImpl implements ArticleVectorStore {
|
||||
|
||||
protected Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Autowired(required = false)
|
||||
private VectorStore vectorStore;
|
||||
|
||||
/**
|
||||
* 保存文章到向量库
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Override
|
||||
public void save(Article article) {
|
||||
if (vectorStore == null) return;
|
||||
Map<String, Object> metadata = MapUtils.newHashMap();
|
||||
metadata.put("id", article.getId());
|
||||
metadata.put("siteCode", article.getCategory().getSite().getSiteCode());
|
||||
metadata.put("categoryCode", article.getCategory().getCategoryCode());
|
||||
metadata.put("categoryName", article.getCategory().getCategoryName());
|
||||
metadata.put("title", article.getTitle());
|
||||
metadata.put("href", article.getHref());
|
||||
metadata.put("keywords", article.getKeywords());
|
||||
metadata.put("description", article.getDescription());
|
||||
metadata.put("url", article.getUrl());
|
||||
metadata.put("status", article.getStatus());
|
||||
metadata.put("createBy", article.getCreateBy());
|
||||
metadata.put("createDate", article.getCreateDate());
|
||||
metadata.put("updateBy", article.getUpdateBy());
|
||||
metadata.put("updateDate", article.getUpdateDate());
|
||||
List<String> attachmentList = ListUtils.newArrayList();
|
||||
String content = article.getTitle() + ", " + article.getKeywords() + ", "
|
||||
+ article.getDescription() + ", " + FlexmarkHtmlConverter.builder()
|
||||
.linkResolverFactory(getHtmlLinkResolverFactory(attachmentList)).build()
|
||||
.convert(article.getArticleData().getContent())
|
||||
+ ", attachment: " + attachmentList;
|
||||
List<Document> documents = List.of(new Document(article.getId(), content, metadata));
|
||||
List<Document> splitDocuments = new TokenTextSplitter().apply(documents);
|
||||
this.delete(article); // 删除原数据
|
||||
ListUtils.pageList(splitDocuments, 10, params -> {
|
||||
vectorStore.add((List<Document>)params[0]); // 增加新数据
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析文章中的连接并提取内容
|
||||
* @author ThinkGem
|
||||
*/
|
||||
private @NotNull HtmlLinkResolverFactory getHtmlLinkResolverFactory(List<String> attachmentList) {
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
return new HtmlLinkResolverFactory() {
|
||||
@Override
|
||||
public @NotNull Set<Class<?>> getAfterDependents() {
|
||||
return Set.of();
|
||||
}
|
||||
@Override
|
||||
public @NotNull Set<Class<?>> getBeforeDependents() {
|
||||
return Set.of();
|
||||
}
|
||||
@Override
|
||||
public boolean affectsGlobalScope() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public HtmlLinkResolver apply(HtmlNodeConverterContext htmlNodeConverterContext) {
|
||||
return (node, context, resolvedLink) -> {
|
||||
if ("a".equalsIgnoreCase(node.nodeName())) {
|
||||
String href = node.attributes().get("href"); String url = href;
|
||||
if (StringUtils.contains(url, "://")) {
|
||||
// 只提取系统允许跳转的附件内容,外部网站内容不进行提取,shiro.allowRedirects 参数设置范围
|
||||
if (ServletUtils.isAllowRedirects(request, url)) {
|
||||
try (InputStream is = HttpClientUtils.getInputStream(url, null)) {
|
||||
if (is != null) {
|
||||
String text = getDocumentText(is);
|
||||
attachmentList.add(url + text);
|
||||
}
|
||||
} catch (IOException | TikaException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String ctxPath = Global.getCtxPath();
|
||||
if (StringUtils.isNotBlank(ctxPath) && StringUtils.startsWith(url, ctxPath)){
|
||||
url = url.substring(ctxPath.length());
|
||||
}
|
||||
try (InputStream is = IOUtils.getFileInputStream(Global.getUserfilesBaseDir(url))){
|
||||
if (is != null) {
|
||||
String text = getDocumentText(is);
|
||||
attachmentList.add(url + text);
|
||||
}
|
||||
} catch (IOException | TikaException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return new ResolvedLink(LinkType.LINK, href);
|
||||
}
|
||||
return resolvedLink;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 获取文章附件中的内容
|
||||
* @author ThinkGem
|
||||
*/
|
||||
private static @NotNull String getDocumentText(InputStream is) throws IOException, TikaException {
|
||||
TikaConfig config = TikaConfig.getDefaultConfig();
|
||||
Tika tika = new Tika(config);
|
||||
Metadata metadata = new Metadata();
|
||||
TikaInputStream stream = TikaInputStream.get(is);
|
||||
MediaType mimetype = tika.getDetector().detect(stream, metadata);
|
||||
if (mimetype != null && StringUtils.equals(mimetype.getType(), "text")) {
|
||||
String text = IOUtils.toString(stream, StandardCharsets.UTF_8);
|
||||
if (StringUtils.isNotBlank(text)) {
|
||||
return FlexmarkHtmlConverter.builder().build().convert(text);
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
String content = tika.parseToString(stream, metadata);
|
||||
return content.lines()
|
||||
.map(String::strip).filter(line -> !line.isEmpty())
|
||||
.reduce((a, b) -> a + System.lineSeparator() + b)
|
||||
.orElse(StringUtils.EMPTY);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除向量库文章
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Override
|
||||
public void delete(Article article) {
|
||||
if (vectorStore == null) return;
|
||||
if (StringUtils.isNotBlank(article.getId())) {
|
||||
vectorStore.delete(new FilterExpressionBuilder().eq("id", article.getId()).build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建向量库文章
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public String rebuild(Article article) {
|
||||
if (vectorStore == null) return null;
|
||||
logger.debug("开始重建向量库。 siteCode: {}, categoryCode: {}",
|
||||
article.getCategory().getSite().getSiteCode(),
|
||||
article.getCategory().getCategoryCode());
|
||||
long start = System.currentTimeMillis();
|
||||
try{
|
||||
article.setIsQueryArticleData(true); // 查询文章内容
|
||||
PageUtils.findList(article, null, e -> {
|
||||
List<Article> list = CmsUtils.getArticleService().findList((Article) e);
|
||||
if (!list.isEmpty()) {
|
||||
list.forEach(this::save);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}catch(Exception ex){
|
||||
logger.error("重建向量库失败", ex);
|
||||
return "重建向量库失败:" + ex.getMessage();
|
||||
}
|
||||
String message = "重建向量库完成! 用时" + TimeUtils.formatTime(System.currentTimeMillis() - start) + "。";
|
||||
logger.debug(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.cms.ai.service;
|
||||
|
||||
import com.jeesite.common.cache.CacheUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.ai.chat.memory.ChatMemoryRepository;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI 对话消息存储
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Service
|
||||
public class CacheChatMemoryRepository implements ChatMemoryRepository {
|
||||
|
||||
private static final String CMS_CHAT_MSG_CACHE = "cmsChatMsgCache";
|
||||
|
||||
@Override
|
||||
public @NotNull List<String> findConversationIds() {
|
||||
return CacheUtils.getCache(CMS_CHAT_MSG_CACHE).keys().stream().map(Object::toString).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<Message> findByConversationId(@NotNull String conversationId) {
|
||||
List<Message> all = CacheUtils.get(CMS_CHAT_MSG_CACHE, conversationId);
|
||||
return all != null ? all : List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAll(@NotNull String conversationId, @NotNull List<Message> messages) {
|
||||
CacheUtils.put(CMS_CHAT_MSG_CACHE, conversationId, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByConversationId(@NotNull String conversationId) {
|
||||
CacheUtils.remove(CMS_CHAT_MSG_CACHE, conversationId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.cms.ai.service;
|
||||
|
||||
import com.jeesite.common.cache.CacheUtils;
|
||||
import com.jeesite.common.collect.ListUtils;
|
||||
import com.jeesite.common.collect.MapUtils;
|
||||
import com.jeesite.common.idgen.IdGen;
|
||||
import com.jeesite.common.lang.DateUtils;
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
import com.jeesite.common.mapper.JsonMapper;
|
||||
import com.jeesite.common.service.BaseService;
|
||||
import com.jeesite.modules.cms.ai.properties.CmsAiProperties;
|
||||
import com.jeesite.modules.sys.entity.Area;
|
||||
import com.jeesite.modules.sys.utils.AreaUtils;
|
||||
import com.jeesite.modules.sys.utils.UserUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.PromptTemplate;
|
||||
import org.springframework.ai.content.Media;
|
||||
import org.springframework.ai.converter.AbstractMessageOutputConverter;
|
||||
import org.springframework.ai.converter.BeanOutputConverter;
|
||||
import org.springframework.ai.converter.MapOutputConverter;
|
||||
import org.springframework.ai.vectorstore.SearchRequest;
|
||||
import org.springframework.ai.vectorstore.VectorStore;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.SignalType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI 聊天服务类
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Service
|
||||
public class CmsAiChatService extends BaseService {
|
||||
|
||||
private static final String CMS_CHAT_CACHE = "cmsChatCache";
|
||||
private static final String[] USER_MESSAGE_SEARCH = new String[]{"{", "}"};
|
||||
private static final String[] USER_MESSAGE_REPLACE = new String[]{"\\{", "\\}"};
|
||||
|
||||
@Autowired
|
||||
private ChatClient chatClient;
|
||||
@Autowired
|
||||
private ChatMemory chatMemory;
|
||||
@Autowired(required = false)
|
||||
private VectorStore vectorStore;
|
||||
@Autowired
|
||||
private CmsAiProperties properties;
|
||||
|
||||
/**
|
||||
* 获取聊天对话消息
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public List<Message> getChatMessage(String conversationId) {
|
||||
if (StringUtils.isBlank(conversationId)) {
|
||||
return List.of();
|
||||
}
|
||||
return chatMemory.get(conversationId);
|
||||
}
|
||||
|
||||
private static String getChatCacheKey() {
|
||||
String key = UserUtils.getUser().getId();
|
||||
if (StringUtils.isBlank(key)) {
|
||||
key = UserUtils.getSession().getId().toString();
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
public Map<String, Map<String, Object>> getChatCacheMap() {
|
||||
Map<String, Map<String, Object>> cache = CacheUtils.get(CMS_CHAT_CACHE, getChatCacheKey());
|
||||
if (cache == null) {
|
||||
cache = MapUtils.newHashMap();
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建或更新聊天对话
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public Map<String, Object> saveChatConversation(String conversationId, String title) {
|
||||
if (StringUtils.isBlank(conversationId)) {
|
||||
conversationId = IdGen.nextId();
|
||||
}
|
||||
if (StringUtils.isBlank(title)) {
|
||||
title = "新对话 " + DateUtils.getTime();
|
||||
}
|
||||
Map<String, Object> map = MapUtils.newHashMap();
|
||||
map.put("id", conversationId);
|
||||
map.put("title", title);
|
||||
Map<String, Map<String, Object>> cache = getChatCacheMap();
|
||||
cache.put(conversationId, map);
|
||||
CacheUtils.put(CMS_CHAT_CACHE, getChatCacheKey(), cache);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除聊天对话
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public void deleteChatConversation(String conversationId) {
|
||||
Map<String, Map<String, Object>> cache = getChatCacheMap();
|
||||
cache.remove(conversationId);
|
||||
CacheUtils.put(CMS_CHAT_CACHE, getChatCacheKey(), cache);
|
||||
chatMemory.clear(conversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天对话,流输出
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public Flux<ChatResponse> chatStream(String conversationId, String message, HttpServletRequest request) {
|
||||
String text = StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE);
|
||||
List<Media> media = ListUtils.newArrayList();
|
||||
// List<FileUpload> fileUploadList = FileUploadUtils.findFileUpload(conversationId, "cms-chat");
|
||||
// for (FileUpload fileUpload : fileUploadList) {
|
||||
// File file = new File(fileUpload.getFileEntity().getFileRealPath());
|
||||
// MediaType mediaType = MediaType.parseMediaType(FileUtils.getContentType(file.getName()));
|
||||
// media.add(Media.builder().mimeType(mediaType).data(file).build());
|
||||
// }
|
||||
UserMessage userMessage = UserMessage.builder().text(text).media(media).build();
|
||||
ChatClient.ChatClientRequestSpec spec = chatClient.prompt().messages(userMessage)
|
||||
.advisors(MessageChatMemoryAdvisor.builder(chatMemory)
|
||||
.conversationId(conversationId)
|
||||
.build());
|
||||
if (vectorStore != null) {
|
||||
spec.advisors(QuestionAnswerAdvisor.builder(vectorStore)
|
||||
.searchRequest(SearchRequest.builder().similarityThreshold(0.6F).topK(6).build())
|
||||
.promptTemplate(new PromptTemplate(properties.getDefaultPromptTemplate()))
|
||||
.build());
|
||||
}
|
||||
return spec.stream()
|
||||
.chatResponse()
|
||||
.doOnNext(response -> {
|
||||
if (response.getResult() != null && StringUtils.isNotBlank(response.getResult().getOutput().getText())) {
|
||||
AssistantMessage assistantMessage = (AssistantMessage)request.getAttribute("assistantMessage");
|
||||
AssistantMessage currAssistantMessage = response.getResult().getOutput();
|
||||
if (assistantMessage == null) {
|
||||
request.setAttribute("assistantMessage", currAssistantMessage);
|
||||
} else {
|
||||
request.setAttribute("assistantMessage", new AssistantMessage(
|
||||
assistantMessage.getText() + currAssistantMessage.getText(),
|
||||
currAssistantMessage.getMetadata()));
|
||||
}
|
||||
}
|
||||
})
|
||||
.doFinally((signalType) -> {
|
||||
if (signalType != SignalType.ON_COMPLETE) {
|
||||
AssistantMessage assistantMessage = (AssistantMessage)request.getAttribute("assistantMessage");
|
||||
if (assistantMessage != null) {
|
||||
chatMemory.add(conversationId, assistantMessage);
|
||||
} else if (signalType == SignalType.CANCEL) {
|
||||
chatMemory.add(conversationId, new AssistantMessage(text("暂无消息,你已主动停止响应。")));
|
||||
}
|
||||
}
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
String errorMessage = error.getMessage();
|
||||
if (error instanceof WebClientResponseException webClientError) {
|
||||
errorMessage = webClientError.getResponseBodyAsString();
|
||||
}
|
||||
AssistantMessage assistantMessage = new AssistantMessage(errorMessage);
|
||||
chatMemory.add(conversationId, assistantMessage);
|
||||
logger.error("Error message: {}", errorMessage);
|
||||
return Flux.just(ChatResponse.builder()
|
||||
.generations(List.of(new Generation(assistantMessage)))
|
||||
.build());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天对话,文本输出
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public String chatText(String message) {
|
||||
return chatClient.prompt()
|
||||
.messages(
|
||||
new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE))
|
||||
)
|
||||
.call()
|
||||
.content();
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天对话,结构化输出(Map)
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public Map<String, Object> chatJson(String message) {
|
||||
return chatClient.prompt()
|
||||
.messages(
|
||||
new SystemMessage("""
|
||||
[{name:'张三', sex:'男', age:'17'}, {name:'李四', sex:'女', age:'18'}],返回 json。
|
||||
"""),
|
||||
new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE))
|
||||
)
|
||||
.call()
|
||||
.responseEntity(
|
||||
new AbstractMessageOutputConverter<Map<String, Object>>(
|
||||
new MappingJackson2MessageConverter(JsonMapper.getInstance())
|
||||
) {
|
||||
final MapOutputConverter mapOutputConverter = new MapOutputConverter();
|
||||
@Override
|
||||
public Map<String, Object> convert(String source) {
|
||||
return mapOutputConverter.convert(source);
|
||||
}
|
||||
@Override
|
||||
public String getFormat() {
|
||||
return mapOutputConverter.getFormat();
|
||||
}
|
||||
}
|
||||
)
|
||||
.getEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天对话,结构化输出(Area)
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public List<Area> chatArea(String message) {
|
||||
List<Area> list = AreaUtils.getAreaAllList();
|
||||
if (list.size() > 10) list = list.subList(0, 10);
|
||||
ChatClient.ChatClientRequestSpec spec = chatClient.prompt()
|
||||
.messages(
|
||||
new SystemMessage(JsonMapper.toJson(list)),
|
||||
new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE))
|
||||
);
|
||||
if (vectorStore != null) {
|
||||
spec.advisors(QuestionAnswerAdvisor.builder(vectorStore)
|
||||
.searchRequest(SearchRequest.builder().similarityThreshold(0.6F).topK(6).build())
|
||||
.promptTemplate(new PromptTemplate(properties.getDefaultPromptTemplate()))
|
||||
.build());
|
||||
}
|
||||
return spec.call()
|
||||
.responseEntity(new BeanOutputConverter<>(new ParameterizedTypeReference<List<Area>>() {},
|
||||
JsonMapper.getInstance()))
|
||||
.getEntity();
|
||||
}
|
||||
|
||||
// public static void main(String[] args) {
|
||||
// String s = """
|
||||
// [{"id":"110000","isNewRecord":false,"createBy":"system","createDate":"2025-01-01T19:25:11Z","updateBy":"system","updateDate":"2025-01-01 19:25","childList":[{"id":"110100","isNewRecord":false,"createBy":"system","createDate":"2025-01-01 19:25","updateBy":"system","updateDate":"2025-01-01 19:25","childList":[{"id":"110101","isNewRecord":false,"areaCode":"110101","areaName":"东城区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110102","isNewRecord":false,"areaCode":"110102","areaName":"西城区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110105","isNewRecord":false,"areaCode":"110105","areaName":"朝阳区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110106","isNewRecord":false,"areaCode":"110106","areaName":"丰台区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110107","isNewRecord":false,"areaCode":"110107","areaName":"石景山区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110108","isNewRecord":false,"areaCode":"110108","areaName":"海淀区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110109","isNewRecord":false,"areaCode":"110109","areaName":"门头沟区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110111","isNewRecord":false,"areaCode":"110111","areaName":"房山区","areaType":"3","isRoot":true,"isTreeLeaf":false}],"areaCode":"110100","areaName":"北京城区","areaType":"2","isRoot":true,"isTreeLeaf":false}],"areaCode":"110000","areaName":"北京市","areaType":"1","isRoot":true,"isTreeLeaf":false}]
|
||||
// """;
|
||||
// JsonMapper jsonMapper = JsonMapper.getInstance();
|
||||
// ParameterizedTypeReference<List<Area>> p = new ParameterizedTypeReference<List<Area>>() {};
|
||||
// List<Area> entity = jsonMapper.fromJsonString(s, jsonMapper.constructType(p.getType()));
|
||||
// System.out.println(entity);
|
||||
// String json = jsonMapper.toJsonString(entity);
|
||||
// System.out.println(json);
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.cms.ai.tools;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* AI 工具调用、Tool calling(需选择支持 Tools 的模型)
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public class CmsAiTools {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CmsAiTools.class);
|
||||
|
||||
/**
|
||||
* 未联网搜索的时候,可获取到服务器时间
|
||||
*/
|
||||
@Tool(description = "当前时间,当前日期,几点了")
|
||||
public String getCurrentDateTime() {
|
||||
String dateTime = "当前日期时间:" + LocalDateTime.now();
|
||||
logger.info(dateTime + " ============== ");
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 你可以询问:打开客厅的灯,关闭卧室的灯(需创建新对话)
|
||||
*/
|
||||
@Tool(description = "房间里的灯打开或关闭")
|
||||
public String turnLight(@ToolParam(description = "房间") String roomName, @ToolParam(description = "开关") boolean on) {
|
||||
String message = roomName + " 房间里的灯被 " + (on ? "打开" : "关闭");
|
||||
logger.info(message + " ============== ");
|
||||
return String.format("""
|
||||
message: %s
|
||||
roomName: %s
|
||||
on: %s
|
||||
""",
|
||||
roomName, on, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.cms.ai.web;
|
||||
|
||||
import com.jeesite.common.config.Global;
|
||||
import com.jeesite.common.web.BaseController;
|
||||
import com.jeesite.modules.cms.ai.service.CmsAiChatService;
|
||||
import com.jeesite.modules.sys.entity.Area;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* AI 聊天控制器类
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("${adminPath}/cms/chat")
|
||||
public class CmsAiChatController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private CmsAiChatService cmsAiChatService;
|
||||
|
||||
/**
|
||||
* 获取聊天对话消息
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@RequestMapping("/message")
|
||||
public List<Message> message(String id) {
|
||||
return cmsAiChatService.getChatMessage(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天对话列表
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@RequestMapping("/list")
|
||||
public Collection<Map<String, Object>> list() {
|
||||
return cmsAiChatService.getChatCacheMap().values().stream()
|
||||
.sorted(Comparator.comparing(map -> (String) map.get("id"),
|
||||
Comparator.reverseOrder())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建或更新聊天对话
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@RequestMapping("/save")
|
||||
public String save(String id, String title) {
|
||||
Map<String, Object> map = cmsAiChatService.saveChatConversation(id, title);
|
||||
return renderResult(Global.TRUE, "保存成功", map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除聊天对话
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@RequestMapping("/delete")
|
||||
public String delete(@RequestParam String id) {
|
||||
cmsAiChatService.deleteChatConversation(id);
|
||||
return renderResult(Global.TRUE, "删除成功", id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天对话,流输出
|
||||
* @author ThinkGem
|
||||
* http://127.0.0.1:8980/js/a/cms/chat/stream?id=1&message=你好
|
||||
*/
|
||||
@RequestMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<ChatResponse> stream(@RequestParam String id, @RequestParam String message, HttpServletRequest request) {
|
||||
return cmsAiChatService.chatStream(id, message, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天对话,文本输出
|
||||
* @author ThinkGem
|
||||
* http://127.0.0.1:8980/js/a/cms/chat/text?message=你好
|
||||
*/
|
||||
@RequestMapping(value = "/text")
|
||||
public String text(@RequestParam String message) {
|
||||
return cmsAiChatService.chatText(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天对话,结构化输出 JSON
|
||||
* @author ThinkGem
|
||||
* http://127.0.0.1:8980/js/a/cms/chat/json?message=张三
|
||||
* http://127.0.0.1:8980/js/a/cms/chat/json?message=打开客厅的灯
|
||||
*/
|
||||
@RequestMapping(value = "/json")
|
||||
public Map<String, Object> json(@RequestParam String message) {
|
||||
return cmsAiChatService.chatJson(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天对话,结构化输出 Entity
|
||||
* @author ThinkGem
|
||||
* http://127.0.0.1:8980/js/a/cms/chat/entity?message=北京
|
||||
*/
|
||||
@RequestMapping(value = "/entity")
|
||||
public List<Area> entity(@RequestParam String message) {
|
||||
return cmsAiChatService.chatArea(message);
|
||||
}
|
||||
|
||||
}
|
||||
12
modules/cms-ai/src/main/resources/application-assistant.yml
Normal file
12
modules/cms-ai/src/main/resources/application-assistant.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
## 重要提示(Tip):
|
||||
|
||||
## 请勿在该配置文件中添加其它任何配置(添加也不会生效)。
|
||||
## 该文件,仅仅是为了让 jeesite-cms-ai.yml 文件,
|
||||
## 在 IDEA 中有一个自动完成及帮助提示,并无其它用意。
|
||||
## 参数配置请在 jeesite-cms-ai.yml 文件中添加。
|
||||
|
||||
spring:
|
||||
config:
|
||||
import:
|
||||
- classpath:config/jeesite-cms-ai.yml
|
||||
189
modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml
Normal file
189
modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml
Normal file
@@ -0,0 +1,189 @@
|
||||
# 温馨提示:不建议直接修改此文件,为了平台升级方便,建议将需要修改的参数值,复制到application.yml里进行覆盖该参数值。
|
||||
|
||||
spring:
|
||||
ai:
|
||||
|
||||
# 模型选择:openai、ollama
|
||||
model:
|
||||
chat: openai
|
||||
embedding: ${spring.ai.model.chat}
|
||||
image: ${spring.ai.model.chat}
|
||||
audio: ${spring.ai.model.chat}
|
||||
|
||||
# 在线大模型【请在 pom.xml 中打开 openai 的注释,并注释上其它模型】
|
||||
openai:
|
||||
|
||||
# 硅基流动
|
||||
base-url: https://api.siliconflow.cn
|
||||
api-key: ${SFLOW_APP_KEY}
|
||||
# 聊天对话模型
|
||||
chat:
|
||||
options:
|
||||
model: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
|
||||
max-tokens: 1024
|
||||
temperature: 0.6
|
||||
top-p: 0.9
|
||||
frequency-penalty: 0
|
||||
# 向量库知识库模型(注意:不同的模型维度不同)
|
||||
embedding:
|
||||
options:
|
||||
model: BAAI/bge-m3
|
||||
dimensions: 512
|
||||
|
||||
# # 模力方舟
|
||||
# base-url: https://ai.gitee.com
|
||||
# api-key: ${GITEE_APP_KEY}
|
||||
# # 聊天对话模型
|
||||
# chat:
|
||||
# options:
|
||||
# model: DeepSeek-R1-Distill-Qwen-14B
|
||||
# max-tokens: 1024
|
||||
# temperature: 0.6
|
||||
# top-p: 0.9
|
||||
# frequency-penalty: 0
|
||||
# #logprobs: true
|
||||
# # 向量库知识库模型(注意:不同的模型维度不同)
|
||||
# embedding:
|
||||
# options:
|
||||
# model: bge-large-zh-v1.5
|
||||
# dimensions: 512
|
||||
|
||||
# # 阿里百炼
|
||||
# base-url: https://dashscope.aliyuncs.com/compatible-mode
|
||||
# api-key: ${BAILIAN_APP_KEY}
|
||||
# # 聊天对话模型
|
||||
# chat:
|
||||
# options:
|
||||
# model: deepseek-r1-distill-llama-8b
|
||||
# max-tokens: 1024
|
||||
# temperature: 0.6
|
||||
# top-p: 0.9
|
||||
# frequency-penalty: 0
|
||||
# #logprobs: true
|
||||
# # 向量库知识库模型(注意:不同的模型维度不同)
|
||||
# embedding:
|
||||
# options:
|
||||
# model: text-embedding-v3
|
||||
# dimensions: 1024
|
||||
|
||||
# 本地大模型配置【请在 pom.xml 中打开 ollama 的注释,并注释上其它模型】
|
||||
ollama:
|
||||
base-url: http://localhost:11434
|
||||
# 聊天对话模型
|
||||
chat:
|
||||
options:
|
||||
model: qwen2.5
|
||||
#model: deepseek-r1:7b
|
||||
max-tokens: 1024
|
||||
temperature: 0.6
|
||||
top-p: 0.7
|
||||
frequency-penalty: 0
|
||||
# 向量库知识库模型(注意:不同的模型维度不同)
|
||||
embedding:
|
||||
# 维度 dimensions 设置为 384
|
||||
#model: all-minilm:33m
|
||||
# 维度 dimensions 设置为 768
|
||||
#model: nomic-embed-text
|
||||
# 维度 dimensions 设置为 1024
|
||||
model: bge-m3
|
||||
|
||||
# 向量数据库配置
|
||||
vectorstore:
|
||||
|
||||
# 向量库类型:chroma、pgvector、elasticsearch、milvus、指定 none 表示不使用向量库
|
||||
type: chroma
|
||||
|
||||
# Chroma 向量数据库【请在 pom.xml 中打开 chroma 的注释,并注释上其它向量库】
|
||||
chroma:
|
||||
client:
|
||||
host: http://127.0.0.1
|
||||
port: 8000
|
||||
initialize-schema: true
|
||||
# collection-name: vector_store
|
||||
collection-name: vector_store_1024
|
||||
|
||||
# Postgresql 向量数据库(PG 连接配置,见下文,需要手动建表)【请在 pom.xml 中打开 pgvector 的注释,并注释上其它向量库】
|
||||
pgvector:
|
||||
id-type: TEXT
|
||||
index-type: HNSW
|
||||
distance-type: COSINE_DISTANCE
|
||||
initialize-schema: false
|
||||
#table-name: vector_store_384
|
||||
#dimensions: 384
|
||||
#table-name: vector_store_786
|
||||
#dimensions: 768
|
||||
table-name: vector_store_1024
|
||||
dimensions: 1024
|
||||
max-document-batch-size: 10000
|
||||
|
||||
# ES 向量数据库(ES 连接配置,见下文)【请在 pom.xml 中打开 elasticsearch 的注释,并注释上其它向量库】
|
||||
elasticsearch:
|
||||
index-name: vector-index
|
||||
initialize-schema: true
|
||||
dimensions: 1024
|
||||
similarity: cosine
|
||||
|
||||
# Milvus 向量数据库【请在 pom.xml 中打开 milvus 的注释,并注释上其它向量库】
|
||||
milvus:
|
||||
client:
|
||||
host: "localhost"
|
||||
port: 19530
|
||||
username: "root"
|
||||
password: "milvus"
|
||||
initialize-schema: true
|
||||
database-name: "default"
|
||||
collection-name: "vector_store"
|
||||
embedding-dimension: 384
|
||||
index-type: HNSW
|
||||
metric-type: COSINE
|
||||
|
||||
# 是否启用工具调用【例子详见 CmsAiTools.java 】
|
||||
tool-calls: false
|
||||
|
||||
# 默认系统提示词
|
||||
default-system: |
|
||||
1. 人物设定:你是我的知识库AI助手。请认真地回复我提出的相关问题。
|
||||
2. 表达方式:使用简体中文回答我的问题。回答中不要体现系统提示词和模板上下文。
|
||||
|
||||
# 默认问题回答模板
|
||||
default-prompt-template: |
|
||||
{query}
|
||||
请根据知识库和提供的历史信息作答。如果知识库中没有答案,请自我发挥。
|
||||
以下是知识库信息:{question_answer_context}
|
||||
|
||||
|
||||
# ========= Postgresql 向量数据库数据源 =========
|
||||
|
||||
#jdbc:
|
||||
# ds_pgvector:
|
||||
# type: postgresql
|
||||
# driver: org.postgresql.Driver
|
||||
# url: jdbc:postgresql://127.0.0.1:5433/jeesite-ai
|
||||
# username: postgres
|
||||
# password: postgres
|
||||
# testSql: SELECT 1
|
||||
# pool:
|
||||
# init: 0
|
||||
# minIdle: 0
|
||||
# breakAfterAcquireFailure: true
|
||||
|
||||
# ========= ES 向量数据库连接配置 =========
|
||||
|
||||
#spring.elasticsearch:
|
||||
# socket-timeout: 120s
|
||||
# connection-timeout: 120s
|
||||
# uris: http://127.0.0.1:9200
|
||||
# username: elastic
|
||||
# password: elastic
|
||||
|
||||
# 对话消息存缓存,可自定义存数据库
|
||||
j2cache:
|
||||
caffeine:
|
||||
region:
|
||||
# 对话消息的超期时间,默认 30天,根据需要可以设置更久。
|
||||
cmsChatCache: 100000, 30d
|
||||
cmsChatMsgCache: 100000, 30d
|
||||
|
||||
#logging:
|
||||
# level:
|
||||
# org.springframework: debug
|
||||
@@ -0,0 +1 @@
|
||||
5.11.0
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.test;
|
||||
|
||||
import com.jeesite.common.mapper.JsonMapper;
|
||||
import com.jeesite.common.tests.BaseSpringContextTests;
|
||||
import com.jeesite.modules.cms.ai.service.CmsAiChatService;
|
||||
import com.jeesite.modules.sys.entity.Area;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI 对话单元测试
|
||||
* @author ThinkGem
|
||||
* @version 2025-06-06
|
||||
*/
|
||||
@ActiveProfiles("test")
|
||||
@SpringBootApplication
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
@SpringBootTest(properties = {"spring.ai.tool-calls=true"})
|
||||
public class AiChatServiceTest extends BaseSpringContextTests {
|
||||
|
||||
@Autowired
|
||||
private CmsAiChatService cmsAiChatService;
|
||||
|
||||
@Test
|
||||
public void test01Text() {
|
||||
logger.info("===== 聊天对话,文本输出");
|
||||
String message = "你好";
|
||||
String text = cmsAiChatService.chatText(message);
|
||||
System.out.println(text);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test02Json() {
|
||||
logger.info("===== 聊天对话,结构化输出 JSON");
|
||||
String message = "张三";
|
||||
Map<String, Object> map = cmsAiChatService.chatJson(message);
|
||||
System.out.println(JsonMapper.toJson(map));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test03Tool() {
|
||||
logger.info("===== 聊天对话,结构化输出 Tool Calling");
|
||||
String message = "打开客厅的灯";
|
||||
Map<String, Object> map = cmsAiChatService.chatJson(message);
|
||||
System.out.println(JsonMapper.toJson(map));
|
||||
message = "关闭客厅的灯";
|
||||
map = cmsAiChatService.chatJson(message);
|
||||
System.out.println(JsonMapper.toJson(map));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test04Entity() {
|
||||
logger.info("===== 聊天对话,结构化输出 Entity");
|
||||
String message = "北京";
|
||||
List<Area> list = cmsAiChatService.chatArea(message);
|
||||
System.out.println(JsonMapper.toJson(list));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
28
modules/cms-ai/src/test/resources/application.yml
Normal file
28
modules/cms-ai/src/test/resources/application.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
# 产品或项目名称、软件开发公司名称
|
||||
productName: JeeSite Demo
|
||||
companyName: ThinkGem
|
||||
|
||||
# 产品版本、版权年份
|
||||
productVersion: V5.13
|
||||
copyrightYear: 2025
|
||||
|
||||
|
||||
# 数据库连接
|
||||
jdbc:
|
||||
|
||||
# Mysql 数据库配置
|
||||
type: mysql
|
||||
driver: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://127.0.0.1:3306/jeesite_v5?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: 123456
|
||||
testSql: SELECT 1
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
config: classpath:logback-test.xml
|
||||
|
||||
# 消息推送
|
||||
msg:
|
||||
enabled: true
|
||||
20
modules/cms-ai/src/test/resources/logback-test.xml
Normal file
20
modules/cms-ai/src/test/resources/logback-test.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="false" scan="false">
|
||||
|
||||
<!-- Logger level setting -->
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
|
||||
<include resource="config/logger-core.xml"/>
|
||||
|
||||
<!-- Console log output -->
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} %clr(%-5p) %clr([%-39logger{39}]){cyan} - %m%n%wEx</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
|
||||
<root level="WARN">
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.jeesite</groupId>
|
||||
<artifactId>jeesite-parent</artifactId>
|
||||
<version>5.13.0-SNAPSHOT</version>
|
||||
<version>5.13.0.springboot3-SNAPSHOT</version>
|
||||
<relativePath>../../parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
Binary file not shown.
@@ -13,9 +13,11 @@ import com.jeesite.common.mybatis.annotation.Table;
|
||||
import com.jeesite.common.mybatis.mapper.query.QueryType;
|
||||
import com.jeesite.modules.cms.utils.CmsUtils;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -66,6 +68,7 @@ import java.util.Date;
|
||||
public class Article extends DataEntity<Article> {
|
||||
|
||||
public static final String DEFAULT_TEMPLATE = "viewArticle"; // 默认文章内容模板
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Category category; // 栏目编码
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
*/
|
||||
package com.jeesite.modules.cms.entity;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import com.jeesite.common.entity.DataEntity;
|
||||
import com.jeesite.common.entity.Extend;
|
||||
import com.jeesite.common.mybatis.annotation.Column;
|
||||
import com.jeesite.common.mybatis.annotation.Table;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 文章详情表Entity
|
||||
* @author 长春叭哥、ThinkGem
|
||||
@@ -26,6 +28,7 @@ import com.jeesite.common.mybatis.annotation.Table;
|
||||
)
|
||||
public class ArticleData extends DataEntity<ArticleData> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String content; // 文章内容
|
||||
private String relation; // 相关文章
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.jeesite.common.entity.DataEntity;
|
||||
import com.jeesite.common.mybatis.annotation.Column;
|
||||
import com.jeesite.common.mybatis.annotation.Table;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 文章推荐位Entity
|
||||
* @author 长春叭哥、ThinkGem
|
||||
@@ -20,6 +22,7 @@ import com.jeesite.common.mybatis.annotation.Table;
|
||||
)
|
||||
public class ArticlePosid extends DataEntity<ArticlePosid> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String articleId; // 内容编号
|
||||
private String postid; // 推荐位置(1轮播图 2首页推荐 3栏目页面)
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.jeesite.common.entity.DataEntity;
|
||||
import com.jeesite.common.mybatis.annotation.Column;
|
||||
import com.jeesite.common.mybatis.annotation.Table;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 文章与标签关系Entity
|
||||
* @author 长春叭哥、ThinkGem
|
||||
@@ -20,6 +22,7 @@ import com.jeesite.common.mybatis.annotation.Table;
|
||||
)
|
||||
public class ArticleTag extends DataEntity<ArticleTag> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String articleId; // 内容编号
|
||||
private String tagName; // 标签名称
|
||||
|
||||
@@ -14,8 +14,10 @@ import com.jeesite.common.mybatis.annotation.Table;
|
||||
import com.jeesite.common.mybatis.mapper.query.QueryType;
|
||||
import com.jeesite.modules.cms.utils.CmsUtils;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -59,6 +61,7 @@ public class Category extends TreeEntity<Category> {
|
||||
public static final String SHOW_MODES_CENTENT_LIST = "2"; // 首栏目内容列表
|
||||
public static final String SHOW_MODES_FIRST_CONTENT = "3"; // 简介类栏目,栏目第一条内容
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String categoryCode; // 栏目编码
|
||||
private String categoryName; // 栏目名称
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
*/
|
||||
package com.jeesite.modules.cms.entity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.jeesite.common.entity.BaseEntity;
|
||||
@@ -43,6 +44,7 @@ import com.jeesite.common.mybatis.mapper.query.QueryType;
|
||||
)
|
||||
public class Comment extends DataEntity<Comment> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Category category;// 分类编号
|
||||
|
||||
@@ -6,6 +6,7 @@ package com.jeesite.modules.cms.entity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
@@ -24,6 +25,7 @@ import com.jeesite.common.lang.ExceptionUtils;
|
||||
*/
|
||||
public class FileTemplate implements Comparable<FileTemplate>, Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final Resource resource;
|
||||
private String fileName;
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
*/
|
||||
package com.jeesite.modules.cms.entity;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import com.jeesite.common.entity.DataEntity;
|
||||
import com.jeesite.common.mybatis.annotation.Column;
|
||||
import com.jeesite.common.mybatis.annotation.Table;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 内容举报表Entity
|
||||
* @author 长春叭哥、ThinkGem
|
||||
@@ -26,6 +28,7 @@ import com.jeesite.common.mybatis.annotation.Table;
|
||||
)
|
||||
public class Report extends DataEntity<Report> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String reportSource; // 举报来源(1文章、2评论)
|
||||
private String reportContent; // 举报内容(文章标题 评论内容)
|
||||
|
||||
@@ -14,9 +14,10 @@ import com.jeesite.modules.cms.utils.CmsUtils;
|
||||
import com.jeesite.modules.sys.utils.CorpUtils;
|
||||
import com.jeesite.modules.sys.utils.UserUtils;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -54,6 +55,7 @@ public class Site extends DataEntity<Site> {
|
||||
*/
|
||||
public static final String DEFAULT_TEMPLATE = "index";
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String siteCode; // 站点编码
|
||||
private String siteName; // 站点名称
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
*/
|
||||
package com.jeesite.modules.cms.entity;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import com.jeesite.common.entity.DataEntity;
|
||||
import com.jeesite.common.mybatis.annotation.Column;
|
||||
import com.jeesite.common.mybatis.annotation.Table;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 内容标签Entity
|
||||
* @author 长春叭哥、ThinkGem
|
||||
@@ -22,6 +24,7 @@ import com.jeesite.common.mybatis.annotation.Table;
|
||||
)
|
||||
public class Tag extends DataEntity<Tag> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String tagName; // 标签名称
|
||||
private Integer clicknum; // 点击次数
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
*/
|
||||
package com.jeesite.modules.cms.entity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.jeesite.common.entity.BaseEntity;
|
||||
@@ -57,6 +58,7 @@ import com.jeesite.common.mybatis.mapper.query.QueryType;
|
||||
)
|
||||
public class VisitLog extends DataEntity<VisitLog> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String requestUrl; // 请求的URL地址
|
||||
private String requestUrlHost; // 受访域名
|
||||
|
||||
@@ -44,6 +44,8 @@ public class ArticleService extends CrudService<ArticleDao, Article> {
|
||||
@Autowired(required = false)
|
||||
private ArticleIndexService articleIndexService;
|
||||
@Autowired(required = false)
|
||||
private ArticleVectorStore articleVectorStore;
|
||||
@Autowired(required = false)
|
||||
private PageCacheService pageCacheService;
|
||||
|
||||
private static final ExecutorService updateExpiredWeightThreadPool = new ThreadPoolExecutor(5, 20,
|
||||
@@ -166,6 +168,10 @@ public class ArticleService extends CrudService<ArticleDao, Article> {
|
||||
if (articleIndexService != null && Article.STATUS_NORMAL.equals(article.getStatus())) {
|
||||
articleIndexService.save(article);
|
||||
}
|
||||
// 保存文章到向量数据库
|
||||
if (articleVectorStore != null && Article.STATUS_NORMAL.equals(article.getStatus())) {
|
||||
articleVectorStore.save(article);
|
||||
}
|
||||
// 清理首页、栏目和文章页面缓存
|
||||
if (pageCacheService != null) {
|
||||
pageCacheService.clearCache(article);
|
||||
@@ -188,6 +194,14 @@ public class ArticleService extends CrudService<ArticleDao, Article> {
|
||||
articleIndexService.delete(article);
|
||||
}
|
||||
}
|
||||
// 保存文章到向量数据库
|
||||
if (articleVectorStore != null) {
|
||||
if (Article.STATUS_NORMAL.equals(article.getStatus())) {
|
||||
articleVectorStore.save(article);
|
||||
} else {
|
||||
articleVectorStore.delete(article);
|
||||
}
|
||||
}
|
||||
// 清理首页、栏目和文章页面缓存
|
||||
if (pageCacheService != null) {
|
||||
pageCacheService.clearCache(article);
|
||||
@@ -221,6 +235,10 @@ public class ArticleService extends CrudService<ArticleDao, Article> {
|
||||
if (articleIndexService != null) {
|
||||
articleIndexService.delete(article);
|
||||
}
|
||||
// 保存文章到向量数据库
|
||||
if (articleVectorStore != null) {
|
||||
articleVectorStore.delete(article);
|
||||
}
|
||||
// 清理首页、栏目和文章页面缓存
|
||||
if (pageCacheService != null) {
|
||||
pageCacheService.clearCache(article);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.cms.service;
|
||||
|
||||
import com.jeesite.common.entity.Page;
|
||||
import com.jeesite.modules.cms.entity.Article;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 文章向量存储服务类
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public interface ArticleVectorStore {
|
||||
|
||||
/**
|
||||
* 保存索引
|
||||
* @author ThinkGem
|
||||
*/
|
||||
void save(Article article);
|
||||
|
||||
/**
|
||||
* 删除索引
|
||||
* @author ThinkGem
|
||||
*/
|
||||
void delete(Article article);
|
||||
|
||||
/**
|
||||
* 重建向量库
|
||||
* @author ThinkGem
|
||||
*/
|
||||
String rebuild(Article article);
|
||||
|
||||
}
|
||||
@@ -27,6 +27,8 @@ public class CategoryService extends TreeService<CategoryDao, Category> {
|
||||
@Autowired(required = false)
|
||||
private ArticleIndexService articleIndexService;
|
||||
@Autowired(required = false)
|
||||
private ArticleVectorStore articleVectorStore;
|
||||
@Autowired(required = false)
|
||||
private PageCacheService pageCacheService;
|
||||
|
||||
/**
|
||||
@@ -125,4 +127,15 @@ public class CategoryService extends TreeService<CategoryDao, Category> {
|
||||
return articleIndexService.rebuild(new Article(category));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建向量数据库
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public String rebuildVectorStore(Category category) {
|
||||
if (articleVectorStore == null) {
|
||||
return text("您好,系统未配置向量数据库");
|
||||
}
|
||||
return articleVectorStore.rebuild(new Article(category));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,6 +27,8 @@ public class SiteService extends CrudService<SiteDao, Site> {
|
||||
@Autowired(required = false)
|
||||
private ArticleIndexService articleIndexService;
|
||||
@Autowired(required = false)
|
||||
private ArticleVectorStore articleVectorStore;
|
||||
@Autowired(required = false)
|
||||
private PageCacheService pageCacheService;
|
||||
|
||||
/**
|
||||
@@ -120,5 +122,16 @@ public class SiteService extends CrudService<SiteDao, Site> {
|
||||
}
|
||||
return articleIndexService.rebuild(new Article(new Category(site)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建向量数据库
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public String rebuildVectorStore(Site site) {
|
||||
if (articleVectorStore == null) {
|
||||
return text("您好,系统未配置向量数据库");
|
||||
}
|
||||
return articleVectorStore.rebuild(new Article(new Category(site)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import com.jeesite.modules.cms.service.CategoryService;
|
||||
import com.jeesite.modules.cms.service.SiteService;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -27,8 +27,8 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import com.jeesite.modules.cms.service.CategoryService;
|
||||
import com.jeesite.modules.cms.service.FileTemplateService;
|
||||
import com.jeesite.modules.cms.utils.CmsUtils;
|
||||
import com.jeesite.modules.sys.utils.DictUtils;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -270,6 +270,17 @@ public class CategoryController extends BaseController {
|
||||
return renderResult(Global.TRUE, categoryService.rebuildIndex(category));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建向量数据库
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@RequiresPermissions("cms:category:rebuildVectorStore")
|
||||
@ResponseBody
|
||||
@RequestMapping(value = "rebuildVectorStore")
|
||||
public String rebuildVectorStore(Category category) {
|
||||
return renderResult(Global.TRUE, categoryService.rebuildVectorStore(category));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取树结构数据
|
||||
* @param excludeCode 排除的Code
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
package com.jeesite.modules.cms.web;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
package com.jeesite.modules.cms.web;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@@ -12,6 +12,8 @@ import com.jeesite.modules.cms.entity.Site;
|
||||
import com.jeesite.modules.cms.service.FileTemplateService;
|
||||
import com.jeesite.modules.cms.service.SiteService;
|
||||
import com.jeesite.modules.sys.utils.UserUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -22,8 +24,6 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
@@ -142,6 +142,17 @@ public class SiteController extends BaseController {
|
||||
public String rebuildIndex(Site site) {
|
||||
return renderResult(Global.TRUE, siteService.rebuildIndex(site));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建向量数据库
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@RequiresPermissions("cms:site:rebuildVectorStore")
|
||||
@ResponseBody
|
||||
@RequestMapping(value = "rebuildVectorStore")
|
||||
public String rebuildVectorStore(Site site) {
|
||||
return renderResult(Global.TRUE, siteService.rebuildVectorStore(site));
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择站点
|
||||
|
||||
@@ -21,8 +21,8 @@ import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ package com.jeesite.modules.cmsfront.web;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@@ -20,5 +20,7 @@ j2cache:
|
||||
#spring:
|
||||
# elasticsearch:
|
||||
# enabled: true
|
||||
# uris: http://Win11:9200
|
||||
# uris: http://127.0.0.1:9200
|
||||
# connection-timeout: 120s
|
||||
# username: elastic
|
||||
# password: elastic
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
|
||||
<a href="${ctxFront}/index" target="_blank" class="btn btn-default" title="${text('访问站点')}"><i class="fa fa-globe"></i> ${text('访问网站')}</a>
|
||||
<% if(hasPermi('cms:article:edit')){ %>
|
||||
<a href="${ctx}/cms/article/form" onclick="$(this).data('href', this.href+'?category.categoryCode='+$('#categoryCode').val())" class="btn btn-primary btnTool" title="${text('新增文章')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
|
||||
<a href="javascript:" onclick="$(this).data('href', '${ctx}/cms/article/form?category.categoryCode='+$('#categoryCode').val())" class="btn btn-primary btnTool" title="${text('新增文章')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
</div>
|
||||
<% } %>
|
||||
<script>
|
||||
$("#inputForm").validate({
|
||||
$('#inputForm').validate({
|
||||
submitHandler: function(form){
|
||||
js.ajaxSubmitForm($(form), function(data){
|
||||
js.showMessage(data.message);
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
</div>
|
||||
<% } %>
|
||||
<script>
|
||||
$("#inputForm").validate({
|
||||
$('#inputForm').validate({
|
||||
submitHandler: function(form){
|
||||
js.ajaxSubmitForm($(form), function(data){
|
||||
js.showMessage(data.message);
|
||||
|
||||
@@ -351,7 +351,7 @@
|
||||
</div>
|
||||
<% } %>
|
||||
<script>
|
||||
$("#inputForm").validate({
|
||||
$('#inputForm').validate({
|
||||
submitHandler: function(form){
|
||||
js.ajaxSubmitForm($(form), function(data){
|
||||
js.showMessage(data.message);
|
||||
|
||||
@@ -25,7 +25,7 @@ if (message != ''){
|
||||
function page(n,s){
|
||||
$("#pageNo").val(n);
|
||||
$("#pageSize").val(s);
|
||||
$("#searchForm").submit();
|
||||
$('#searchForm').submit();
|
||||
return false;
|
||||
}
|
||||
function sel(th, val){
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.jeesite</groupId>
|
||||
<artifactId>jeesite-parent</artifactId>
|
||||
<version>5.13.0-SNAPSHOT</version>
|
||||
<version>5.13.0.springboot3-SNAPSHOT</version>
|
||||
<relativePath>../../parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<dependency>
|
||||
<groupId>com.dameng</groupId>
|
||||
<artifactId>DmJdbcDriver18</artifactId>
|
||||
<version>8.1.3.62</version>
|
||||
<version>${dameng.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
<dependency>
|
||||
<groupId>cn.com.kingbase</groupId>
|
||||
<artifactId>kingbase8</artifactId>
|
||||
<version>8.6.1</version>
|
||||
<version>${kingbase.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -135,6 +135,13 @@
|
||||
<artifactId>logstash-logback-encoder</artifactId>
|
||||
<version>${logstash-logback.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 热部署工具 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.jeesite.common.shiro.session.SessionDAO;
|
||||
import com.jeesite.common.shiro.session.SessionManager;
|
||||
import com.jeesite.common.shiro.web.ShiroFilterFactoryBean;
|
||||
import com.jeesite.common.shiro.web.WebSecurityManager;
|
||||
import jakarta.servlet.Filter;
|
||||
import org.apache.shiro.cache.CacheManager;
|
||||
import org.apache.shiro.realm.Realm;
|
||||
import org.apache.shiro.realm.ldap.JndiLdapContextFactory;
|
||||
@@ -34,7 +35,6 @@ import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
package com.jeesite.common.shiro.authc;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -13,6 +14,7 @@ import java.util.Map;
|
||||
*/
|
||||
public class LdapToken extends FormToken {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public LdapToken() {
|
||||
|
||||
@@ -16,10 +16,10 @@ import org.apache.shiro.authc.AuthenticationToken;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.web.util.WebUtils;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* CAS过滤器
|
||||
|
||||
@@ -20,6 +20,10 @@ import com.jeesite.common.web.CookieUtils;
|
||||
import com.jeesite.common.web.http.ServletUtils;
|
||||
import com.jeesite.modules.sys.entity.*;
|
||||
import com.jeesite.modules.sys.utils.*;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
import org.apache.shiro.authc.IncorrectCredentialsException;
|
||||
@@ -33,10 +37,6 @@ import org.apache.shiro.web.util.WebUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -8,9 +8,9 @@ import com.jeesite.common.config.Global;
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
import org.apache.shiro.web.filter.AccessControlFilter;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 内部系统访问过滤器
|
||||
|
||||
@@ -6,8 +6,8 @@ package com.jeesite.common.shiro.filter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
package com.jeesite.common.shiro.filter;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.session.SessionException;
|
||||
|
||||
@@ -9,15 +9,15 @@ import com.jeesite.common.config.Global;
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
import com.jeesite.common.web.http.ServletUtils;
|
||||
import com.jeesite.common.web.http.wrapper.GetHttpServletRequestWrapper;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authz.UnauthorizedException;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.web.util.WebUtils;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
*/
|
||||
package com.jeesite.common.shiro.filter;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,10 +6,10 @@ package com.jeesite.common.shiro.filter;
|
||||
|
||||
import com.jeesite.common.config.Global;
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.jeesite.modules.sys.entity.User;
|
||||
import com.jeesite.modules.sys.service.UserService;
|
||||
import com.jeesite.modules.sys.utils.LogUtils;
|
||||
import com.jeesite.modules.sys.utils.UserUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.authc.AuthenticationInfo;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
@@ -22,8 +23,6 @@ import org.apache.shiro.authz.AuthorizationInfo;
|
||||
import org.apache.shiro.session.Session;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 系统认证授权实现类
|
||||
* @author ThinkGem
|
||||
|
||||
@@ -30,8 +30,8 @@ import org.jasig.cas.client.validation.TicketValidationException;
|
||||
import org.jasig.cas.client.validation.TicketValidator;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.ValidationException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,11 +4,17 @@
|
||||
*/
|
||||
package com.jeesite.common.shiro.realm;
|
||||
|
||||
import javax.naming.AuthenticationNotSupportedException;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.ldap.LdapContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.jeesite.common.shiro.authc.FormToken;
|
||||
import com.jeesite.common.shiro.authc.LdapToken;
|
||||
import com.jeesite.common.utils.SpringUtils;
|
||||
import com.jeesite.common.web.http.ServletUtils;
|
||||
import com.jeesite.modules.sys.entity.Log;
|
||||
import com.jeesite.modules.sys.entity.User;
|
||||
import com.jeesite.modules.sys.service.EmpUserService;
|
||||
import com.jeesite.modules.sys.service.UserService;
|
||||
import com.jeesite.modules.sys.utils.LogUtils;
|
||||
import com.jeesite.modules.sys.utils.UserUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.authc.AuthenticationInfo;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
@@ -21,20 +27,13 @@ import org.apache.shiro.realm.ldap.LdapContextFactory;
|
||||
import org.apache.shiro.realm.ldap.LdapUtils;
|
||||
import org.apache.shiro.session.Session;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.jeesite.common.shiro.authc.FormToken;
|
||||
import com.jeesite.common.shiro.authc.LdapToken;
|
||||
import com.jeesite.common.utils.SpringUtils;
|
||||
import com.jeesite.common.web.http.ServletUtils;
|
||||
import com.jeesite.modules.sys.entity.Log;
|
||||
import com.jeesite.modules.sys.entity.User;
|
||||
import com.jeesite.modules.sys.service.EmpUserService;
|
||||
import com.jeesite.modules.sys.service.UserService;
|
||||
import com.jeesite.modules.sys.utils.LogUtils;
|
||||
import com.jeesite.modules.sys.utils.UserUtils;
|
||||
import javax.naming.AuthenticationNotSupportedException;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.ldap.LdapContext;
|
||||
|
||||
/**
|
||||
* 系统认证授权实现类
|
||||
|
||||
@@ -6,9 +6,8 @@ import com.jeesite.common.ueditor.define.AppInfo;
|
||||
import com.jeesite.common.ueditor.define.BaseState;
|
||||
import com.jeesite.common.ueditor.define.State;
|
||||
import com.jeesite.common.ueditor.hunter.FileManager;
|
||||
import com.jeesite.common.ueditor.hunter.ImageHunter;
|
||||
import com.jeesite.common.ueditor.upload.Uploader;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.util.Date;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import com.jeesite.common.ueditor.define.AppInfo;
|
||||
import com.jeesite.common.ueditor.define.BaseState;
|
||||
import com.jeesite.common.ueditor.define.MultiState;
|
||||
import com.jeesite.common.ueditor.define.State;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
package com.jeesite.common.ueditor.hunter;
|
||||
|
||||
import com.jeesite.common.config.Global;
|
||||
import com.jeesite.common.ueditor.PathFormat;
|
||||
import com.jeesite.common.ueditor.define.*;
|
||||
import com.jeesite.common.ueditor.upload.StorageManager;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
@@ -15,6 +8,19 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.jeesite.common.config.Global;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import com.jeesite.common.ueditor.PathFormat;
|
||||
import com.jeesite.common.ueditor.define.AppInfo;
|
||||
import com.jeesite.common.ueditor.define.BaseState;
|
||||
import com.jeesite.common.ueditor.define.MIMEType;
|
||||
import com.jeesite.common.ueditor.define.MultiState;
|
||||
import com.jeesite.common.ueditor.define.State;
|
||||
import com.jeesite.common.ueditor.upload.StorageManager;
|
||||
|
||||
/**
|
||||
* 图片抓取器
|
||||
*
|
||||
|
||||
@@ -6,9 +6,9 @@ import com.jeesite.common.ueditor.define.AppInfo;
|
||||
import com.jeesite.common.ueditor.define.BaseState;
|
||||
import com.jeesite.common.ueditor.define.FileType;
|
||||
import com.jeesite.common.ueditor.define.State;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
public final class Base64Uploader {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user