Compare commits
872 Commits
4e61a4afe9
...
updateMenu
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b580788f1 | |||
| 7b6ff3bcc6 | |||
| 8e5303838e | |||
| 28f92b6138 | |||
| 8ece9617d6 | |||
| a5cf647f2b | |||
| 758e49973b | |||
| 876f8b700d | |||
| 53b777c7af | |||
| 5e749f6de6 | |||
| dcd0907cdd | |||
| 3f16621313 | |||
| 03b3c8141a | |||
| 509683386f | |||
| e3b6c3d8d6 | |||
| 29cdd7b3e7 | |||
| ee864c882a | |||
| 10e449c001 | |||
| 89d60dc528 | |||
| ce35086316 | |||
| e7376086f9 | |||
| 4588f3f4c0 | |||
| 97898aa395 | |||
| b4a7c6ba90 | |||
| 4d6a225f96 | |||
| 6d9b5a727d | |||
| a79e08f6bc | |||
| e49ab63a0b | |||
| 2b3c86e763 | |||
| 1263b4812d | |||
| 755969b069 | |||
| dbe8556741 | |||
| b10e7b4ca1 | |||
| 844b3addf3 | |||
| 77532caf47 | |||
| ff9cc76ebc | |||
| d125510267 | |||
| 46ac312ddf | |||
| c45d98856e | |||
| f2c0932e2e | |||
| b8334c7764 | |||
| c709e04c88 | |||
| 509fc8cc28 | |||
| 48338278f0 | |||
| 833ea7af7d | |||
| f5cea23daa | |||
| a29c9c8d4d | |||
| 511bf33914 | |||
| 4450f82717 | |||
| f62c542684 | |||
| bb8c951651 | |||
| 9f5b3918d0 | |||
| 84d9291763 | |||
| fcbc5a30c4 | |||
| 6f4eb21deb | |||
| 387a7488ab | |||
| 6819eb5f80 | |||
| 0f1ad34342 | |||
| 35d1cfc668 | |||
| a6dc40760d | |||
| 2486bb4c6e | |||
| a59a452f89 | |||
| 930bff6cba | |||
| 70c9997c82 | |||
| 0db70d3d73 | |||
| 2884699ff2 | |||
| 849eb802bc | |||
| 4ce8ea6a3a | |||
| 7e1db73fb0 | |||
| 46299e5732 | |||
| 6032869417 | |||
| 955d7e39e6 | |||
| ecc0595ec3 | |||
| 6b22ed8b0a | |||
| 9b7db3214e | |||
| 71b3c77028 | |||
| c7bd709240 | |||
| a80c38f87f | |||
| 7b91443532 | |||
| a9b9e12732 | |||
| 99ecb29f3c | |||
| 4fe7a435c6 | |||
| 18be0f00a3 | |||
| 67bbf94359 | |||
| 9dfea176b3 | |||
| 09d3b06e7a | |||
| 8b60d13c0a | |||
| 188172692b | |||
| 556f3b6535 | |||
| 0b81a8dc63 | |||
| 982ec18490 | |||
| 0c38c42bfe | |||
| 2f0b323680 | |||
| 35d2a10c0e | |||
| 9c7f9ad980 | |||
| 79a2dbfcd3 | |||
| b5b7324f7c | |||
| b171ec6af0 | |||
| 85c7ded162 | |||
| b39aa7be71 | |||
| 8ae5614ce9 | |||
| 03cf7ebac7 | |||
| 3efa899a97 | |||
| bf1b9cf98f | |||
| e800ff3ac9 | |||
| f0802432ed | |||
| c96808efc1 | |||
| e972efb247 | |||
| f782edd68b | |||
| 45702ef838 | |||
| 4e3faa7a23 | |||
| 40559d4d73 | |||
| 7816fec7aa | |||
| 37c95ab0bd | |||
| 3e38cf9abb | |||
| 4aaf384ce8 | |||
| 6a06d8596d | |||
| be89a6fe9e | |||
| 40b51e5e9d | |||
| 4b14ea5c7f | |||
| a44777d7b2 | |||
| 492fba5fd3 | |||
| 07b921d630 | |||
| 13d33e9d53 | |||
| 4f38db5fde | |||
| a868c7b5cb | |||
| d37b41c967 | |||
| b8fb9413de | |||
| fa9fcb1167 | |||
| ebd88a999c | |||
| 97079abdef | |||
| 94aaa90775 | |||
| 1e01c53c52 | |||
| 6c9e23e662 | |||
| 8a27da9975 | |||
| e160cc5ff3 | |||
| 80b5f94cd4 | |||
| c9c81bb761 | |||
| af90b0f879 | |||
| b51a3ccf28 | |||
| f07db8c368 | |||
| 5e9d36400c | |||
| 8f4a3e167a | |||
| 3ccd54a03b | |||
| 03177f58f2 | |||
| b0c875c9c4 | |||
| bfe74bdb4c | |||
| cad5ba1101 | |||
| 45d143ace3 | |||
| df08eb3dd6 | |||
| 8c292850c2 | |||
| 12d3924e8a | |||
| d32947afe2 | |||
| 9eecf67374 | |||
| d7fd7d6cc4 | |||
| 77482c71b3 | |||
| 66893410e9 | |||
| fbbc4846de | |||
| aac6efc568 | |||
| 209e624fc2 | |||
| fe79bf282c | |||
| 18cbc0655b | |||
| 6b59d264b4 | |||
| 74785a768e | |||
| 32aafaa2db | |||
| 06617d94a7 | |||
| 1b2f9aa755 | |||
| 04407d2f84 | |||
| c0fef2521f | |||
| 04430d8853 | |||
| 875b436eea | |||
| 2c1ca98e4e | |||
| 765490b51c | |||
| b116db0bcd | |||
| e2892b460b | |||
| 30b1586a4a | |||
| 15d86526ac | |||
| cdf5f8bf5b | |||
| 9bb41d903e | |||
| d5f1c22bd4 | |||
| d2dcf656df | |||
| 7191a60841 | |||
| 3f21d39653 | |||
| b520ea6afe | |||
| e6283bee1c | |||
| f6e1d0c73e | |||
| c7e06ade6d | |||
| 3d85b10475 | |||
| e26dfc098a | |||
| c3ab46c8c8 | |||
| e37d118d0c | |||
| a6c92e8ba9 | |||
| 02f324e1d4 | |||
| 96392dbf57 | |||
| e39e788ddc | |||
| e17ab16821 | |||
| 8a928e852d | |||
| 397f8ee768 | |||
| 8e25b416ec | |||
| e6b0527e44 | |||
| 403848cb4b | |||
| a4b1820e1c | |||
| c2afe05e81 | |||
| 1902ba9d59 | |||
| 1d1a0e45c6 | |||
| 72c775b1e3 | |||
| bec0848cd9 | |||
| 4e78886a82 | |||
| 292a56bccd | |||
| 3f12c15cf8 | |||
| 1ab47ccb57 | |||
| 321fc67c4f | |||
| b6e8031cef | |||
| d7265b08a8 | |||
| 2f0ad8a8cd | |||
| 22c58031d2 | |||
| 473f6943d5 | |||
| 08ef9ccfea | |||
| 94b5736241 | |||
| 95b4bc85b3 | |||
| dfaadd4977 | |||
| f372f22934 | |||
| bc91c44596 | |||
| 480d7be81d | |||
| 1c0be9b5ee | |||
| 9c7d8ffc96 | |||
| 167cbf256b | |||
| c3ce1c22d3 | |||
| 891c205233 | |||
| 3b184abc96 | |||
| 9a20cf0e43 | |||
| faf434e4d2 | |||
| 73988b8828 | |||
| 0c3b14e010 | |||
| d764fbe473 | |||
| 073f704c0e | |||
| 5f64f3aef4 | |||
| baa492b799 | |||
| a668381101 | |||
| 4f2ac72edc | |||
| b501965d25 | |||
| 57b662837d | |||
| f11ed11629 | |||
| cdcbfaa1a9 | |||
| ef775a3cfb | |||
| 818b67b5aa | |||
| a0554e47fd | |||
| 49c9ef8878 | |||
| 3265a58813 | |||
| 5a9044786c | |||
| d8d0828f8e | |||
| 8bade20502 | |||
| be944595e3 | |||
| 1eaf29b282 | |||
| 4bb24fe464 | |||
| e704522965 | |||
| 3440b885ee | |||
| 3e85e79bdd | |||
| 90b0eb82f4 | |||
| ef2c1ff455 | |||
| 074fb5bf86 | |||
| 3f4b127cdf | |||
| e93ea2fdfd | |||
| cf368db9af | |||
| 034d01d604 | |||
| af247d917b | |||
| a51d836dc2 | |||
| 1f233c142a | |||
| 4b3a56eb5f | |||
| 2b25ec1483 | |||
| 0736552eb7 | |||
| d99d59ffa0 | |||
| 5da05dcf24 | |||
| fceb06eca8 | |||
| 24219b6942 | |||
| 8566231790 | |||
| b96cc95741 | |||
| 8478cd2997 | |||
| 1bbc1e52ae | |||
| 9221cb50a3 | |||
| e8e15f4a98 | |||
| 0941197ca9 | |||
| 23dc2f2759 | |||
| 7321db3419 | |||
| be7576f8ea | |||
| cc72b1bd64 | |||
| e1d713b23e | |||
| 5ebc46d2e1 | |||
| 0da63144e7 | |||
| 347bd92b69 | |||
| 6d859c6a8c | |||
| 733b95632b | |||
| b905320413 | |||
| 8d4059bbf4 | |||
| b1b8ced0a8 | |||
| 9d3c212856 | |||
| ceec388f5f | |||
| 44f604ff57 | |||
| fae9c61f07 | |||
| e48b27715f | |||
| feb0e49da3 | |||
| 245cdb40f7 | |||
| a2d2431bce | |||
| 048a175526 | |||
| d9d2688e24 | |||
| 15382b5aba | |||
| 8500469918 | |||
| 864eb1462f | |||
| 86b44282ea | |||
| 7ccf5e4469 | |||
| 137ab4e802 | |||
| a941db7870 | |||
| 35c32d68c3 | |||
| 359600004d | |||
| 910801b057 | |||
| 7aa9d519a0 | |||
| 5d7b438745 | |||
| 204177afa0 | |||
| 1d8d9c0236 | |||
| 8ceade6227 | |||
| 5714079de3 | |||
| f21a298b8f | |||
| 09fbf237f0 | |||
| a6fd3e0cef | |||
| e47cd3eba7 | |||
| a6976aaac4 | |||
| dd970d200c | |||
| d1d6c443de | |||
| d26a99cdbb | |||
| 90dbc34a93 | |||
| 0408afb67e | |||
| 587cabc166 | |||
| 30f916f8eb | |||
| fae046885e | |||
| 05569d0471 | |||
| 8efeff20d4 | |||
| bc21aa3f41 | |||
| fedbb99b18 | |||
| 4b2b68d9ef | |||
| a663e30759 | |||
| b6a2d41f57 | |||
| cd87783ad0 | |||
| 7876a82ca4 | |||
| 0a18a0892a | |||
| b2e1cce667 | |||
| 64c1c84ed3 | |||
| f2301264ea | |||
| c83af6df6d | |||
| 22d9dfe138 | |||
| bcee164f48 | |||
| f6d8cb3ba7 | |||
| 659146a2cf | |||
| c17c084095 | |||
| 5962f694e2 | |||
| 373528b432 | |||
| e9f5e0fa03 | |||
| b88a92b7e1 | |||
| fb72063369 | |||
| 9809213a12 | |||
| 87e1cb7473 | |||
| c456ae215f | |||
| 091d7bfc0e | |||
| c9f80fe498 | |||
| 7a3deba52a | |||
| eb015fcecc | |||
| d9fab2a4a2 | |||
| 7f125b4548 | |||
| 88dcecc88c | |||
| 5458329252 | |||
| c4e275f5a8 | |||
| f8f89fd96e | |||
| b8ffa41a30 | |||
| f99cd08d57 | |||
| 45cae080a0 | |||
| 84a8f49e95 | |||
| e725991ece | |||
| c2a06a729c | |||
| 4a5e50a7a2 | |||
| 9b30a7bcec | |||
| ec383e44db | |||
| d3909f131f | |||
| a444d4c953 | |||
| 3c01ec861b | |||
| 365cf00644 | |||
| 4faff3b0bc | |||
| 866f2336c3 | |||
| e4132ea540 | |||
| 53208c36cf | |||
| 8f2a3e6e50 | |||
| b67a7d5370 | |||
| 07509c8e15 | |||
| e6c58a64ac | |||
| abb6b8c13a | |||
| de492728bc | |||
| 82fa732db6 | |||
| 275d640263 | |||
| 7e47b8a74f | |||
| d4301da0ec | |||
| fe5b5473dd | |||
| 0126d44761 | |||
| dbefd88e41 | |||
| 9a3b7ebe54 | |||
| 81be4a862c | |||
| 43f2db9f7e | |||
| abefa90408 | |||
| 08f48b7817 | |||
| 6b8ace60d4 | |||
| b997dd5f00 | |||
| 6174743858 | |||
| 82d55d7188 | |||
| aec5eacd0b | |||
| a320b85965 | |||
| b61a7c153d | |||
| fb9b01cf34 | |||
| 32f134873a | |||
| f4220be9d6 | |||
| 8252fd7216 | |||
| 57855f4307 | |||
| 6784eafe6e | |||
| 0b42c1d6a6 | |||
| 4b37a7327f | |||
| 0287f1e4ce | |||
| 5d8af1cab8 | |||
| 123896f08b | |||
| a8a198b51f | |||
| f00b98714a | |||
| 4ff87f3996 | |||
| 5f3ae0f9f1 | |||
| 786c864a27 | |||
| c61e802b85 | |||
| 9a568799f4 | |||
| 8aa38063bf | |||
| c4a11ec245 | |||
| 169b76589c | |||
| 199f51ea21 | |||
| 23572dfc07 | |||
| 570b0ce316 | |||
| f953a96c36 | |||
| 25c4eee464 | |||
| b209ef1fab | |||
| 77e9f4d9a2 | |||
| e73c808bc3 | |||
| edf0d1a5db | |||
| cc23a308c1 | |||
| bc891327c9 | |||
| 39bedfeb92 | |||
| 56fc4ff83e | |||
| 524ed30728 | |||
| 584304e744 | |||
| 56418600c5 | |||
| 40e57b18cb | |||
| ced6cb219c | |||
| 218ec5ea95 | |||
| 1c601bd68e | |||
| 7d6eba719b | |||
| 8cfc34dbcb | |||
| 23a749973d | |||
| 4b96702dc9 | |||
| a1af8711ef | |||
| 9e8cff931b | |||
| 5bccd71bdc | |||
| 544f35a601 | |||
| f920d4976e | |||
| cdcd665d43 | |||
| 08de61e455 | |||
| 025c3115b7 | |||
| 9e366554b7 | |||
| d934abf0fe | |||
| e16e9133e2 | |||
| d7854a35d7 | |||
| 97e0dd467f | |||
| e58a99e696 | |||
| 0d5a9eb505 | |||
| 45ac1817e1 | |||
| f2d4ff4237 | |||
| a4cc5c14f3 | |||
| 5be571cc30 | |||
| ff656dd046 | |||
| 20574d0037 | |||
| 42cf396e93 | |||
| 2a30b05dd7 | |||
| 4ecee185bf | |||
| 48ab59c67d | |||
| 806e8f3391 | |||
| 61244321a7 | |||
| ff1e613316 | |||
| 03161751fc | |||
| 9ebf4a3e5f | |||
| 3ba13e3ef8 | |||
| 8d3853fe6b | |||
| dbc09a62ea | |||
| f3fa78475c | |||
| c565771283 | |||
| bac8488244 | |||
| a5f661b558 | |||
| ceecec97c7 | |||
| c6ae8a4c00 | |||
| 37d0c776c0 | |||
| f8eea0f63f | |||
| 59c749ab2a | |||
| eeeba2bf4b | |||
| a9ce42101f | |||
| 13de88265f | |||
| a464a1236d | |||
| fce5d0e7fc | |||
| 66ba43d030 | |||
| 1aa77e5eda | |||
| 037016fc13 | |||
| 99f0026552 | |||
| 535262d721 | |||
| fbcb9ca3f2 | |||
| 2a7a20b966 | |||
| 98fdab0dba | |||
| bd71335ae6 | |||
| 7f746fc250 | |||
| 901c8785fe | |||
| e4e9718acb | |||
| 738101f374 | |||
| a7befd7278 | |||
| 52e968c313 | |||
| de9d7d34d6 | |||
| 9f0105d88a | |||
| 80ec8ff86d | |||
| f3473fe5d5 | |||
| 856f3f334b | |||
| 0b216a4101 | |||
| c7338b45ad | |||
| c93b1b752e | |||
| e38074bb25 | |||
| be0f425e14 | |||
| f24e33b1c7 | |||
| 48ea20581c | |||
| e7fa22e573 | |||
| 83b7b32035 | |||
| 6a0be071b8 | |||
| 7bdf8f53b9 | |||
| 03b249afe7 | |||
| 2e5e42fd84 | |||
| f9d9785536 | |||
| a06511e0bf | |||
| 31aa56d34b | |||
| 2b4517760f | |||
| c318d0b10b | |||
| ec54b4ff52 | |||
| 965a0cc90e | |||
| ebddc5c51f | |||
| a323844440 | |||
| 6bcddf50da | |||
| c72275859f | |||
| 9561ee9323 | |||
| b7a52de2d2 | |||
| bfc3ea60fd | |||
| 1acc676b0f | |||
| d6a378f711 | |||
| b4f56b6c79 | |||
| b5d2b3df06 | |||
| 4042b4a441 | |||
| 1fd3da3e2a | |||
| 0d84c49ca4 | |||
| cecfb60e71 | |||
| aec8667edc | |||
| e930cd3b7c | |||
| 373906bde7 | |||
| bca9745e60 | |||
| 6808057111 | |||
| 9f1da9e6c0 | |||
| d8838f8e01 | |||
| d92d37c646 | |||
| 6398a28974 | |||
| cce9ef98d8 | |||
| 467a972a6d | |||
| 00e5f5ede6 | |||
| 1b590bbcbd | |||
| 40d53dffba | |||
| db3af72d5f | |||
| 9604cab4d6 | |||
| 536b25d773 | |||
| e87cbce77a | |||
| f34afd962d | |||
| 98f23e2c02 | |||
| b9507e1fd7 | |||
| 1ceef7f1d1 | |||
| 0fb3fd70a6 | |||
| aab0a5e0b8 | |||
| f9d1a7a489 | |||
| 87e58ca4af | |||
| d6263c6430 | |||
| 253ef81066 | |||
| 2f4bec42a3 | |||
| 3879ce28b6 | |||
| 691b5341c7 | |||
| 4a50dc6ff0 | |||
| d7e4c65afb | |||
| 61b39d89de | |||
| 2497527c77 | |||
| 79f74434f7 | |||
| bd0fe7228b | |||
| 66b45df3fb | |||
| 38d6832f2c | |||
| 8e51d67071 | |||
| 942ca4202a | |||
| 255a202c02 | |||
| 1033ee01fb | |||
| 201f7bd3a7 | |||
| 8a7726e216 | |||
| bc85fe64a4 | |||
| 549fbe92de | |||
| 270071a5f3 | |||
| 09cbb6a8c5 | |||
| f825b9f968 | |||
| f7b9d2f431 | |||
| 742df7279b | |||
| 684209aa36 | |||
| d033b505e2 | |||
| 59623f53a0 | |||
| b9cf8ea3de | |||
| 45761415fa | |||
| 4dca396d11 | |||
| 0efaf12849 | |||
| 51686c212e | |||
| 866c53c6cc | |||
| 809494ffca | |||
| 7824803bf6 | |||
| 5df3bb7baf | |||
| c7dcddb58e | |||
| cc64f16231 | |||
| 26129e7433 | |||
| e7aec88abf | |||
| f820edebb5 | |||
| 9d586f7b77 | |||
| 00e9fa1ea3 | |||
| 9a698857ce | |||
| b47cdb5732 | |||
| 3bf7a1201f | |||
| bb38b6a6e1 | |||
| 11b5908d8c | |||
| 6c96c6f534 | |||
| 22a7500048 | |||
| 412db4d348 | |||
| 6a200b391e | |||
| 6e0e867e57 | |||
| 66d2f24256 | |||
| 69e8e4169b | |||
| 33d051164e | |||
| 2d18345087 | |||
| ef10a4f0a4 | |||
| 9784aa7ee3 | |||
| af069f3fa5 | |||
| d8f5cc34ea | |||
| 0870003967 | |||
| fb4ce63f59 | |||
| 502cc55143 | |||
| 372c825844 | |||
| ca256a28a1 | |||
| 666e402d5d | |||
| 3630c13f59 | |||
| 334ff20129 | |||
| 3371ef9c1c | |||
| ed6b4c608f | |||
| cdd9266452 | |||
| a1450c4796 | |||
| a1ba33c06d | |||
| b0493e7de9 | |||
| 7b00d67cf7 | |||
| 3eae13e67a | |||
| 9fe3cd494f | |||
| 2c554321bb | |||
| 39038889b6 | |||
| cbf4b3b22e | |||
| 3d3df16817 | |||
| c66d5f0789 | |||
| d644df94dc | |||
| eb6a7f2373 | |||
| fa625aff6d | |||
| e64897246a | |||
| 6c138d1f4d | |||
| fdd1b8dfa0 | |||
| 9f7b6d77d3 | |||
| 45999b0c03 | |||
| 944d36e5e7 | |||
| f87c67349d | |||
| 259ddd46cf | |||
| 570e7dd019 | |||
| 4a27c7d179 | |||
| f2912f0661 | |||
| cd4e1db7bd | |||
| 23eab88db1 | |||
| a7004ac125 | |||
| f02cd59135 | |||
| d0e40bdd33 | |||
| a34c07c64e | |||
| af83c40d98 | |||
| 1bb59cc2f1 | |||
| e1b9b0e8c5 | |||
| 1de11bb110 | |||
| 84c9946337 | |||
| bc41175162 | |||
| 1e76e4ffe1 | |||
| 2952d978d2 | |||
| 256e3f9c0d | |||
| faff11ad6f | |||
| a177ab3067 | |||
| 7f1971248a | |||
| 4d6c7fe2c0 | |||
| 278004788b | |||
| a8744cc4cc | |||
| 20dc9b3e85 | |||
| d0c1a1337e | |||
| 70553dff79 | |||
| 8af5fb52ec | |||
| b754e3ffc0 | |||
| d72f763545 | |||
| 683f58cdc8 | |||
| 61dfb371f8 | |||
| 9772f1a024 | |||
| 5b6d3bf758 | |||
| a2167e8227 | |||
| 49c5689b4a | |||
| 53413e2ddf | |||
| 6c0deb23c6 | |||
| 5cd202ceda | |||
| 71078cd36f | |||
| 81c8938076 | |||
| ce99cdd623 | |||
| 9ad44e727e | |||
| 275ed8946c | |||
| 325547f201 | |||
| 7fdd75da1f | |||
| 5f8d494154 | |||
| f1dffb54eb | |||
| 8e4200323a | |||
| 40a8f0770a | |||
| 7fcf5ef303 | |||
| fc9b64fc36 | |||
| c7400d25fe | |||
| 48a08ac729 | |||
| 31dcd00bbc | |||
| 4e3d62af4a | |||
| 1e7ffe891a | |||
| f706e73c30 | |||
| a64ff2ff32 | |||
| 15b3176893 | |||
| ee7981a462 | |||
| 5c86398b75 | |||
| 5f3b24868f | |||
| b8f223aea1 | |||
| bf6ea00b26 | |||
| 0c4a697fea | |||
| 6d7aa8ca17 | |||
| caa134dfc4 | |||
| 23f0f2fd16 | |||
| 25690503e5 | |||
| 928ba9783b | |||
| 6224b42479 | |||
| 1d2ec60e68 | |||
| b35ff07ba4 | |||
| 4ffb639d33 | |||
| 6110374e67 | |||
| 19f8e10714 | |||
| 42c5f46f75 | |||
| 646b503d47 | |||
| f6ed7d3d7b | |||
| 223f1aee98 | |||
| 8b4e56e6e8 | |||
| b2ec975f22 | |||
| 267d62e5a5 | |||
| 579fd04687 | |||
| 4354d0a687 | |||
| 0f9beafd9c | |||
| c07e9f76a6 | |||
| e42e81ab06 | |||
| 5d6d9020e7 | |||
| 7ee081c39c | |||
| d66d44460b | |||
| 9d0c66b2d3 | |||
| 300579d32c | |||
| e8838a91d0 | |||
| 2f57979a41 | |||
| e075f8f6e5 | |||
| 6032c6fcba | |||
| da20820627 | |||
| e7c50c770e | |||
| ef68e7b7a8 | |||
| d9b879667b | |||
| 3e1b698ea8 | |||
| af81b1b740 | |||
| 2d7f9ea42d | |||
| 5b94ae37cd | |||
| 26cf862d6e | |||
| f7cd344a0b | |||
| 89c89cbfcd | |||
| 868616ea26 | |||
| b5cf663a2b | |||
| 4c536ca269 | |||
| f0f30ce841 | |||
| 4d2a2186c9 | |||
| c277e5f80d | |||
| 21aae1f8c0 | |||
| 7e90c101e8 | |||
| c3899ae6a2 | |||
| c662e1aa4d | |||
| 91f01dc012 | |||
| ce972d030f | |||
| dd025df653 | |||
| d77e12ee4e | |||
| 9afda7305c | |||
| 974b11f8d3 | |||
| a1d1aaff49 | |||
| 9402c3c9f3 | |||
| 8e9307bf00 | |||
| c68d6cb53c | |||
| 8e84195e98 | |||
| 542c4e91ac | |||
| 47b0a4b087 | |||
| 6ace8b0dc2 | |||
| 1e33027d4f | |||
| 9fbc9dbc45 | |||
| 8bf12d04cd | |||
| 00dcc00b05 | |||
| 0b29401112 | |||
| e8c78865cb | |||
| a8cb16ab3f | |||
| 2c45762c66 | |||
| 056b28af31 | |||
| 7dd6d97a3e | |||
| 697beb67c4 | |||
| 4d627af3a1 | |||
| ab332c462f | |||
| 0f3d1e38be | |||
| 4392a287cc | |||
| f38538be33 | |||
| ede1e501b4 | |||
| 4a2b62cf92 | |||
| 325f392e8f | |||
| 5b991396c2 | |||
| c75563b46a | |||
| 724ebf8dbd | |||
| a52b9078a0 | |||
| 9d682a3290 | |||
| abad289c2b | |||
| 820188863e | |||
| 113b5debc9 | |||
| 6b4cd4ae0d | |||
| 261dd0b643 | |||
| 71f3810e51 | |||
| fa835684d4 | |||
| e19ef3003a | |||
| 8a18223d06 | |||
| c90828f98e | |||
| 2ecb0063bf | |||
| 998547e63f | |||
| 78829ef5e7 | |||
| da0dd8f78f | |||
| 96d0406931 | |||
| d7616960c6 | |||
| 66e8495859 | |||
| 95c2858a64 | |||
| 5cbb0c630b | |||
| e0cc521291 | |||
| ff9f49b1d9 | |||
| b738bb821d | |||
| 2cea57646d | |||
| ab5cd491d6 | |||
| 3f3e20a64b | |||
| f5d9cb7fc1 | |||
| 5b05d2eb40 | |||
| fbffc18a9f | |||
| 490820d080 | |||
| 8e9b7c9b14 | |||
| ae3738c098 | |||
| 127059e934 | |||
| 742e67af23 |
2
xinnengyuan/.gitignore
vendored
2
xinnengyuan/.gitignore
vendored
@ -53,3 +53,5 @@ logs/
|
||||
docs
|
||||
/file
|
||||
.idea/
|
||||
chat-memory/
|
||||
queries/
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>21</java.version>
|
||||
<mybatis.version>3.5.16</mybatis.version>
|
||||
<springdoc.version>2.8.4</springdoc.version>
|
||||
<springdoc.version>2.3.0</springdoc.version>
|
||||
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
||||
<easyexcel.version>4.0.3</easyexcel.version>
|
||||
<velocity.version>2.3</velocity.version>
|
||||
@ -86,6 +86,15 @@
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>menu</id>
|
||||
<properties>
|
||||
<profiles.active>menu</profiles.active>
|
||||
<logging.level>info</logging.level>
|
||||
<monitor.username>ruoyi</monitor.username>
|
||||
<monitor.password>123456</monitor.password>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>prod</id>
|
||||
<properties>
|
||||
@ -307,7 +316,6 @@
|
||||
<artifactId>snail-job-client-job-core</artifactId>
|
||||
<version>${snailjob.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 加密包引入 -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
|
||||
@ -6,6 +6,7 @@ import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -28,6 +29,8 @@ import org.dromara.common.social.utils.SocialUtils;
|
||||
import org.dromara.common.sse.dto.SseMessageDto;
|
||||
import org.dromara.common.sse.utils.SseMessageUtils;
|
||||
import org.dromara.common.tenant.helper.TenantHelper;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||
import org.dromara.system.domain.bo.SysTenantBo;
|
||||
import org.dromara.system.domain.vo.SysClientVo;
|
||||
import org.dromara.system.domain.vo.SysTenantVo;
|
||||
@ -41,16 +44,20 @@ import org.dromara.web.domain.vo.TenantListVo;
|
||||
import org.dromara.web.service.IAuthStrategy;
|
||||
import org.dromara.web.service.SysLoginService;
|
||||
import org.dromara.web.service.SysRegisterService;
|
||||
import org.dromara.websocket.domain.ChatGroup;
|
||||
import org.dromara.websocket.service.Impl.ChatGroupServiceImpl;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 认证
|
||||
@ -72,6 +79,7 @@ public class AuthController {
|
||||
private final ISysSocialService socialUserService;
|
||||
private final ISysClientService clientService;
|
||||
private final ScheduledExecutorService scheduledExecutorService;
|
||||
private final ChatGroupServiceImpl chatGroupService;
|
||||
|
||||
|
||||
/**
|
||||
@ -103,10 +111,7 @@ public class AuthController {
|
||||
|
||||
// Long userId = LoginHelper.getUserId();
|
||||
// scheduledExecutorService.schedule(() -> {
|
||||
// SseMessageDto dto = new SseMessageDto();
|
||||
// dto.setMessage("欢迎登录新能源项目管理系统");
|
||||
// dto.setUserIds(List.of(userId));
|
||||
// SseMessageUtils.publishMessage(dto);
|
||||
// chatGroupService.createSystem(userId,client.getClientKey());
|
||||
// }, 5, TimeUnit.SECONDS);
|
||||
return R.ok(loginVo);
|
||||
}
|
||||
@ -193,6 +198,17 @@ public class AuthController {
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/app/register")
|
||||
public R<Void> appRegister(@Validated @RequestBody RegisterBody user) {
|
||||
if("sms".equals(user.getGrantType())){
|
||||
registerService.appSmsRegister(user);
|
||||
}else {
|
||||
registerService.appRegister(user);
|
||||
}
|
||||
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录页面租户下拉框
|
||||
*
|
||||
|
||||
@ -5,6 +5,8 @@ import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -57,19 +59,26 @@ public class CaptchaController {
|
||||
*/
|
||||
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
|
||||
@GetMapping("/resource/sms/code")
|
||||
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
|
||||
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber,String type) {
|
||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
|
||||
String code = RandomUtil.randomNumbers(4);
|
||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
// 验证码模板id 自行处理 (查数据库或写死均可)
|
||||
String templateId = "";
|
||||
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||
map.put("code", code);
|
||||
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
||||
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
|
||||
map.put("1", code);
|
||||
map.put("2", Constants.CAPTCHA_EXPIRATION.toString());
|
||||
String configName;
|
||||
if("login".equals(type)){
|
||||
configName = "config2";
|
||||
}else{
|
||||
configName = "config3";
|
||||
}
|
||||
SmsBlend smsBlend = SmsFactory.getSmsBlend(configName);
|
||||
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, map);
|
||||
if (!smsResponse.isSuccess()) {
|
||||
log.error("验证码短信发送异常 => {}", smsResponse);
|
||||
return R.fail(smsResponse.getData().toString());
|
||||
return R.fail(parseData(smsResponse));
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
@ -133,4 +142,35 @@ public class CaptchaController {
|
||||
return R.ok(captchaVo);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static String parseData(SmsResponse smsResponse ) {
|
||||
try {
|
||||
JSONObject json = JSONUtil.parseObj(smsResponse.getData());
|
||||
// 核心:用 JsonUtil 解析 data,通过键路径 "Response.SendStatusSet[0].Code" 提取字段
|
||||
// 键路径规则:层级用 "." 分隔,列表索引用 "[0]" 表示(第 1 个元素)
|
||||
String code = json.getByPath("Response.SendStatusSet[0].Code", String.class);
|
||||
System.out.println("错误码:" + code); // 输出:FailedOperation.InsufficientBalanceInSmsPackage
|
||||
return convert(code);
|
||||
} catch (Exception e) {
|
||||
return "短信发送未知错误";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String convert(String code) {
|
||||
return switch (code) {
|
||||
case "FailedOperation.InsufficientBalanceInSmsPackage" -> "套餐包余量不足,请购买套餐包";
|
||||
case "InternalError.SendAndRecvFail" -> "短信收发超时,请检查您的网络是否有波动";
|
||||
case "InvalidParameterValue.IncorrectPhoneNumber" -> "手机号格式错误";
|
||||
case "LimitExceeded.AppCountryOrRegionDailyLimit" -> "业务短信国家/地区日下发条数超过设定的上限";
|
||||
case "LimitExceeded.AppDailyLimit" -> "业务短信日下发条数超过设定的上限";
|
||||
case "LimitExceeded.PhoneNumberDailyLimit" -> "单个手机号日下发短信条数超过设定的上限";
|
||||
case "UnauthorizedOperation.ServiceSuspendDueToArrears" -> "欠费被停止服务";
|
||||
case "UnsupportedOperation.UnsupportedRegion" -> "不支持该地区短信下发";
|
||||
default -> "短信发送未知错误";
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -55,7 +55,8 @@ public class UserActionListener implements SaTokenListener {
|
||||
String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
|
||||
String tenantId = (String) loginModel.getExtra(LoginHelper.TENANT_KEY);
|
||||
dto.setUserName(username);
|
||||
dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
|
||||
String clientId = (String) loginModel.getExtra(LoginHelper.CLIENT_KEY);
|
||||
dto.setClientKey(clientId);
|
||||
dto.setDeviceType(loginModel.getDevice());
|
||||
dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
|
||||
TenantHelper.dynamic(tenantId, () -> {
|
||||
@ -75,7 +76,7 @@ public class UserActionListener implements SaTokenListener {
|
||||
SpringUtils.context().publishEvent(logininforEvent);
|
||||
// 更新登录信息
|
||||
loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
|
||||
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
|
||||
log.info("user doLogin, userId:{}, token:{}, clientid{}", loginId, tokenValue, clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -77,6 +77,90 @@ public class SysRegisterService {
|
||||
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
public void appRegister(RegisterBody registerBody) {
|
||||
String tenantId = registerBody.getTenantId();
|
||||
String username = registerBody.getPhonenumber();
|
||||
String password = registerBody.getPassword();
|
||||
// 校验用户类型是否存在
|
||||
String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
|
||||
// 校验密码是否符合要求
|
||||
// String pattern = "^(?!.*\\s)(?!^[a-zA-Z]+$)(?!^[0-9]+$)(?!^[^a-zA-Z0-9]+$)(?!^[a-zA-Z0-9]+$).{8,18}$";
|
||||
// boolean isValid = password.matches(pattern);
|
||||
// if (!isValid) {
|
||||
// throw new UserException("注册失败,密码需满足8–18位,包含大小写字母、数字、特殊字符中的至少三种组合");
|
||||
// }
|
||||
|
||||
SysUserBo sysUser = new SysUserBo();
|
||||
sysUser.setUserName(username);
|
||||
sysUser.setNickName(username);
|
||||
sysUser.setPhonenumber(username);
|
||||
sysUser.setPassword(BCrypt.hashpw(password));
|
||||
sysUser.setUserType(userType);
|
||||
sysUser.setEmail(registerBody.getEmail());
|
||||
|
||||
SysUser sysUserByPhonenumber = userMapper.selectDefFlagUser(username);
|
||||
if(sysUserByPhonenumber != null){
|
||||
sysUser.setUserId(sysUserByPhonenumber.getUserId());
|
||||
userMapper.updateDefFlag(sysUser);
|
||||
userMapper.updateConstructionUser(sysUserByPhonenumber.getUserId());
|
||||
}else {
|
||||
|
||||
boolean exist = TenantHelper.dynamic(tenantId, () -> {
|
||||
return userMapper.exists(new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getPhonenumber, sysUser.getPhonenumber()));
|
||||
});
|
||||
if (exist) {
|
||||
throw new UserException("user.register.save.error", username);
|
||||
}
|
||||
boolean regFlag = userService.registerUser(sysUser, tenantId);
|
||||
if (!regFlag) {
|
||||
throw new UserException("user.register.error");
|
||||
}
|
||||
}
|
||||
|
||||
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 短信注册
|
||||
*/
|
||||
public void appSmsRegister(RegisterBody registerBody) {
|
||||
String tenantId = registerBody.getTenantId();
|
||||
String username = registerBody.getPhonenumber();
|
||||
String smsCode = registerBody.getSmsCode();
|
||||
|
||||
validateSmsCode(tenantId, username, smsCode);
|
||||
|
||||
// 校验用户类型是否存在
|
||||
String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
|
||||
|
||||
// 验证码开关
|
||||
SysUserBo sysUser = new SysUserBo();
|
||||
sysUser.setUserName(username);
|
||||
sysUser.setNickName(username);
|
||||
sysUser.setPhonenumber(username);
|
||||
sysUser.setUserType(userType);
|
||||
|
||||
boolean exist = TenantHelper.dynamic(tenantId, () -> {
|
||||
return userMapper.exists(new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getPhonenumber, sysUser.getPhonenumber()));
|
||||
});
|
||||
if (exist) {
|
||||
throw new UserException("user.register.save.error", username);
|
||||
}
|
||||
boolean regFlag = userService.registerUser(sysUser, tenantId);
|
||||
if (!regFlag) {
|
||||
throw new UserException("user.register.error");
|
||||
}
|
||||
|
||||
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*
|
||||
@ -98,6 +182,18 @@ public class SysRegisterService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验短信验证码
|
||||
*/
|
||||
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
|
||||
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
|
||||
if (StringUtils.isBlank(code)) {
|
||||
recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
return code.equals(smsCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
port: 8899
|
||||
--- # 监控中心配置
|
||||
spring.boot.admin.client:
|
||||
# 增加客户端开关
|
||||
@ -26,12 +30,18 @@ snail-job:
|
||||
# 随主应用端口漂移
|
||||
port: 2${server.port}
|
||||
# 客户端ip指定
|
||||
host:
|
||||
host: 127.0.0.1
|
||||
# RPC类型: netty, grpc
|
||||
rpc-type: grpc
|
||||
|
||||
--- # 数据源配置
|
||||
spring:
|
||||
ai:
|
||||
dashscope:
|
||||
api-key: sk-8d8df92fcbac4bd2922edba30b0bb8fa
|
||||
chat:
|
||||
options:
|
||||
model: qwen3-max
|
||||
datasource:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
|
||||
@ -52,14 +62,38 @@ spring:
|
||||
url: jdbc:mysql://192.168.110.2:13386/xinnengyuandev?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: xinnengyuandev
|
||||
password: StRWCZdZirysNSs2
|
||||
# url: jdbc:mysql://192.168.110.2:13386/xinnengyuan?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
# username: xinnengyuan
|
||||
# password: mEZPC5Sdf3r2HENi
|
||||
# 从库数据源
|
||||
# slave:
|
||||
# lazy: true
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://192.168.110.2:13386/zmkgdev?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# username: zmkgdev
|
||||
# password: JhYxREf25AXdy3h8
|
||||
# url: jdbc:mysql://192.168.110.2:13386/zmkgc?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# username: zmkgc
|
||||
# password: nWKDKRNRT48tFBdh
|
||||
# slave1:
|
||||
# lazy: true
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://192.168.110.2:13386/zmkgprod?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# username: zmkgprod
|
||||
# password: MaY8nehwWkJriWPm
|
||||
# slave2:
|
||||
# lazy: true
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://192.168.110.2:13386/zmkgc?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# username: zmkgc
|
||||
# password: nWKDKRNRT48tFBdh
|
||||
# slave:
|
||||
# lazy: true
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://192.168.110.2:13386/zmkgprod?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# username: zmkgprod
|
||||
# password: MaY8nehwWkJriWPm
|
||||
# oracle:
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: oracle.jdbc.OracleDriver
|
||||
@ -102,7 +136,7 @@ spring.data:
|
||||
# 端口,默认为6379
|
||||
port: 9287
|
||||
# 数据库索引
|
||||
database: 10
|
||||
database: 16
|
||||
# redis 密码必须配置
|
||||
password: syar23rdsaagdrsa
|
||||
# 连接超时时间
|
||||
@ -161,7 +195,7 @@ sms:
|
||||
# 配置源类型用于标定配置来源(interface,yaml)
|
||||
config-type: yaml
|
||||
# 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制
|
||||
restricted: true
|
||||
restricted: false
|
||||
# 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效
|
||||
minute-max: 1
|
||||
# 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效
|
||||
@ -180,12 +214,45 @@ sms:
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
config2:
|
||||
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
|
||||
# 登录
|
||||
supplier: tencent
|
||||
access-key-id: 您的accessKey
|
||||
access-key-secret: 您的accessKeySecret
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2491779
|
||||
config3:
|
||||
# 注册
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2491776
|
||||
config4:
|
||||
# 质量工单逾期
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2534747
|
||||
config5:
|
||||
# 设计图纸
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2534750
|
||||
config6:
|
||||
# 安全工单
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2534848
|
||||
|
||||
|
||||
--- # 三方授权
|
||||
@ -274,7 +341,10 @@ dxf2GeoJson:
|
||||
file-name: main.exe
|
||||
ys7:
|
||||
app-key: 3acf9f1a43dc4209841e0893003db0a2
|
||||
app-secret: 4bbf3e9394f55d3af6e3af27b2d3db36
|
||||
app-secret: 09e29c70ae1161fbc3ce2030fc09ba2e
|
||||
job:
|
||||
capture-enabled: false # 控制是否启用萤石抓拍任务
|
||||
device-sync-enabled: false # 控制是否同步萤石设备
|
||||
#ys7:
|
||||
# app-key: 081b0d6d5f7f4de8bc5c7fa350fb26ec
|
||||
# app-secret: caa37b9f60ef02deb57e563bc190e6db
|
||||
@ -287,7 +357,25 @@ sparta:
|
||||
id-card:
|
||||
encrypt-key: 7ae260d150a14027d2238a1cf80a48ef
|
||||
recognizer:
|
||||
url: http://192.168.110.5:50070
|
||||
url: http://192.168.110.5:50071
|
||||
|
||||
qrCode:
|
||||
url: http://192.168.110.151:7788
|
||||
# 无人机大图
|
||||
drone:
|
||||
url: http://192.168.110.2:9512
|
||||
# 聊天服务
|
||||
chat:
|
||||
server:
|
||||
port: 19099
|
||||
# rabbitmq 配置
|
||||
rabbitmq:
|
||||
exchange-name: dev-normal-exchange
|
||||
queue-name: dev-normal-queue
|
||||
routing-key: dev.normal.routing.key
|
||||
delay-exchange-name: dev-delay-queue
|
||||
delay-queue-name: dev-delay-exchange
|
||||
delay-routing-key: dev.delay.routing.key
|
||||
dead-letter-exchange: dev-dlx-exchange
|
||||
dead-letter-queue: dev-dlx-queue
|
||||
dead-letter-routing-key: dev.dlx.routing.key
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
port: 9918
|
||||
--- # 临时文件存储位置 避免临时文件被系统清理报错
|
||||
spring.servlet.multipart.location: /ruoyi/server/temp
|
||||
|
||||
@ -35,6 +39,12 @@ snail-job:
|
||||
|
||||
--- # 数据源配置
|
||||
spring:
|
||||
ai:
|
||||
dashscope:
|
||||
api-key: sk-8d8df92fcbac4bd2922edba30b0bb8fa
|
||||
chat:
|
||||
options:
|
||||
model: qwen3-max
|
||||
datasource:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
|
||||
@ -105,7 +115,7 @@ spring.data:
|
||||
# 端口,默认为6379
|
||||
port: 9287
|
||||
# 数据库索引
|
||||
database: 6
|
||||
database: 17
|
||||
# redis 密码必须配置
|
||||
password: syar23rdsaagdrsa
|
||||
# 连接超时时间
|
||||
@ -164,7 +174,7 @@ sms:
|
||||
# 配置源类型用于标定配置来源(interface,yaml)
|
||||
config-type: yaml
|
||||
# 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制
|
||||
restricted: true
|
||||
restricted: false
|
||||
# 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效
|
||||
minute-max: 1
|
||||
# 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效
|
||||
@ -183,12 +193,45 @@ sms:
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
config2:
|
||||
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
|
||||
# 登录
|
||||
supplier: tencent
|
||||
access-key-id: 您的accessKey
|
||||
access-key-secret: 您的accessKeySecret
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2491779
|
||||
config3:
|
||||
# 注册
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2491776
|
||||
config4:
|
||||
# 质量工单逾期
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2534747
|
||||
config5:
|
||||
# 设计图纸
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2534750
|
||||
config6:
|
||||
# 安全工单
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2534848
|
||||
|
||||
--- # 三方授权
|
||||
justauth:
|
||||
@ -275,8 +318,11 @@ weather:
|
||||
dxf2GeoJson:
|
||||
file-name: main
|
||||
ys7:
|
||||
app-key: 3acf9f1a43dc4209841e0893003db0a2
|
||||
app-secret: 4bbf3e9394f55d3af6e3af27b2d3db36
|
||||
app-key: xxx
|
||||
app-secret: xxx
|
||||
job:
|
||||
capture-enabled: false # 控制是否启用萤石抓拍任务
|
||||
device-sync-enabled: false # 控制是否同步萤石设备
|
||||
# 斯巴达算法
|
||||
sparta:
|
||||
url: http://119.3.204.120:8040
|
||||
@ -286,7 +332,25 @@ sparta:
|
||||
id-card:
|
||||
encrypt-key: 7ae260d150a14027d2238a1cf80a48ef
|
||||
recognizer:
|
||||
url: http://192.168.110.5:50070
|
||||
url: http://192.168.110.5:50071
|
||||
|
||||
qrCode:
|
||||
url: http://xny.yj-3d.com:7171
|
||||
url: http://xny.yj-3d.com:7788
|
||||
# 无人机大图
|
||||
drone:
|
||||
url: http://192.168.110.2:9512
|
||||
# 聊天服务
|
||||
chat:
|
||||
server:
|
||||
port: 18088
|
||||
# rabbitmq 配置
|
||||
rabbitmq:
|
||||
exchange-name: local-normal-exchange
|
||||
queue-name: local-normal-queue
|
||||
routing-key: local.normal.routing.key
|
||||
delay-exchange-name: local-delay-queue
|
||||
delay-queue-name: local-delay-exchange
|
||||
delay-routing-key: local.delay.routing.key
|
||||
dead-letter-exchange: local-dlx-exchange
|
||||
dead-letter-queue: local-dlx-queue
|
||||
dead-letter-routing-key: local.dlx.routing.key
|
||||
|
||||
356
xinnengyuan/ruoyi-admin/src/main/resources/application-menu.yml
Normal file
356
xinnengyuan/ruoyi-admin/src/main/resources/application-menu.yml
Normal file
@ -0,0 +1,356 @@
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
port: 9528
|
||||
--- # 临时文件存储位置 避免临时文件被系统清理报错
|
||||
spring.servlet.multipart.location: /ruoyi/server/temp
|
||||
|
||||
--- # 监控中心配置
|
||||
spring.boot.admin.client:
|
||||
# 增加客户端开关
|
||||
enabled: false
|
||||
url: http://localhost:9090/admin
|
||||
instance:
|
||||
service-host-type: IP
|
||||
metadata:
|
||||
username: ${spring.boot.admin.client.username}
|
||||
userpassword: ${spring.boot.admin.client.password}
|
||||
username: @monitor.username@
|
||||
password: @monitor.password@
|
||||
|
||||
--- # snail-job 配置
|
||||
snail-job:
|
||||
enabled: false
|
||||
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
|
||||
group: "ruoyi_group"
|
||||
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config`表
|
||||
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
|
||||
server:
|
||||
host: 127.0.0.1
|
||||
port: 17888
|
||||
# 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
|
||||
namespace: ${spring.profiles.active}
|
||||
# 随主应用端口漂移
|
||||
port: 2${server.port}
|
||||
# 客户端ip指定
|
||||
host:
|
||||
# RPC类型: netty, grpc
|
||||
rpc-type: grpc
|
||||
|
||||
--- # 数据源配置
|
||||
spring:
|
||||
ai:
|
||||
dashscope:
|
||||
api-key: sk-8d8df92fcbac4bd2922edba30b0bb8fa
|
||||
chat:
|
||||
options:
|
||||
model: qwen3-max
|
||||
datasource:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
|
||||
dynamic:
|
||||
# 性能分析插件(有性能损耗 不建议生产环境使用)
|
||||
p6spy: false
|
||||
# 设置默认的数据源或者数据源组,默认值即为 master
|
||||
primary: master
|
||||
# 严格模式 匹配不到数据源则报错
|
||||
strict: true
|
||||
datasource:
|
||||
# 主库数据源
|
||||
master:
|
||||
type: ${spring.datasource.type}
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
|
||||
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
|
||||
url: jdbc:mysql://192.168.110.2:13386/xinnengyuanmenu?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
username: xinnengyuanmenu
|
||||
password: 2RkXFG8ZE6r5LL7B
|
||||
# # 从库数据源
|
||||
# slave:
|
||||
# lazy: true
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# username:
|
||||
# password:
|
||||
# oracle:
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: oracle.jdbc.OracleDriver
|
||||
# url: jdbc:oracle:thin:@//localhost:1521/XE
|
||||
# username: ROOT
|
||||
# password: root
|
||||
# postgres:
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: org.postgresql.Driver
|
||||
# url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
|
||||
# username: root
|
||||
# password: root
|
||||
# sqlserver:
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
# url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
|
||||
# username: SA
|
||||
# password: root
|
||||
hikari:
|
||||
# 最大连接池数量
|
||||
maxPoolSize: 20
|
||||
# 最小空闲线程数量
|
||||
minIdle: 10
|
||||
# 配置获取连接等待超时的时间
|
||||
connectionTimeout: 30000
|
||||
# 校验超时时间
|
||||
validationTimeout: 5000
|
||||
# 空闲连接存活最大时间,默认10分钟
|
||||
idleTimeout: 600000
|
||||
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
|
||||
maxLifetime: 1800000
|
||||
# 多久检查一次连接的活性
|
||||
keepaliveTime: 30000
|
||||
|
||||
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
|
||||
spring.data:
|
||||
redis:
|
||||
# 地址
|
||||
host: 192.168.110.2
|
||||
# 端口,默认为6379
|
||||
port: 9287
|
||||
# 数据库索引
|
||||
database: 21
|
||||
# redis 密码必须配置
|
||||
password: syar23rdsaagdrsa
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
# 是否开启ssl
|
||||
ssl.enabled: false
|
||||
|
||||
# redisson 配置
|
||||
redisson:
|
||||
# redis key前缀
|
||||
keyPrefix:
|
||||
# 线程池数量
|
||||
threads: 16
|
||||
# Netty线程池数量
|
||||
nettyThreads: 32
|
||||
# 单节点配置
|
||||
singleServerConfig:
|
||||
# 客户端名称
|
||||
clientName: ${ruoyi.name}
|
||||
# 最小空闲连接数
|
||||
connectionMinimumIdleSize: 32
|
||||
# 连接池大小
|
||||
connectionPoolSize: 64
|
||||
# 连接空闲超时,单位:毫秒
|
||||
idleConnectionTimeout: 10000
|
||||
# 命令等待超时,单位:毫秒
|
||||
timeout: 3000
|
||||
# 发布和订阅连接池大小
|
||||
subscriptionConnectionPoolSize: 50
|
||||
|
||||
--- # mail 邮件发送
|
||||
mail:
|
||||
enabled: false
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
# 是否需要用户名密码验证
|
||||
auth: true
|
||||
# 发送方,遵循RFC-822标准
|
||||
from: xxx@163.com
|
||||
# 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
|
||||
user: xxx@163.com
|
||||
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
|
||||
pass: xxxxxxxxxx
|
||||
# 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
|
||||
starttlsEnable: true
|
||||
# 使用SSL安全连接
|
||||
sslEnable: true
|
||||
# SMTP超时时长,单位毫秒,缺省值不超时
|
||||
timeout: 0
|
||||
# Socket连接超时值,单位毫秒,缺省值不超时
|
||||
connectionTimeout: 0
|
||||
|
||||
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
|
||||
# https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用
|
||||
sms:
|
||||
# 配置源类型用于标定配置来源(interface,yaml)
|
||||
config-type: yaml
|
||||
# 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制
|
||||
restricted: false
|
||||
# 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效
|
||||
minute-max: 1
|
||||
# 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效
|
||||
account-max: 30
|
||||
# 以下配置来自于 org.dromara.sms4j.provider.config.BaseConfig类中
|
||||
blends:
|
||||
# 唯一ID 用于发送短信寻找具体配置 随便定义别用中文即可
|
||||
# 可以同时存在两个相同厂商 例如: ali1 ali2 两个不同的阿里短信账号 也可用于区分租户
|
||||
config1:
|
||||
# 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
|
||||
supplier: alibaba
|
||||
# 有些称为accessKey有些称之为apiKey,也有称为sdkKey或者appId。
|
||||
access-key-id: 您的accessKey
|
||||
# 称为accessSecret有些称之为apiSecret
|
||||
access-key-secret: 您的accessKeySecret
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
config2:
|
||||
# 登录
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2491779
|
||||
config3:
|
||||
# 注册
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2491776
|
||||
config4:
|
||||
# 质量工单逾期
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2534747
|
||||
config5:
|
||||
# 设计图纸
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2534750
|
||||
config6:
|
||||
# 安全工单
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2534848
|
||||
|
||||
--- # 三方授权
|
||||
justauth:
|
||||
# 前端外网访问地址
|
||||
address: http://localhost:80
|
||||
type:
|
||||
maxkey:
|
||||
# maxkey 服务器地址
|
||||
# 注意 如下均配置均不需要修改 maxkey 已经内置好了数据
|
||||
server-url: http://sso.maxkey.top
|
||||
client-id: 876892492581044224
|
||||
client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
|
||||
redirect-uri: ${justauth.address}/social-callback?source=maxkey
|
||||
topiam:
|
||||
# topiam 服务器地址
|
||||
server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol
|
||||
client-id: 449c4*********937************759
|
||||
client-secret: ac7***********1e0************28d
|
||||
redirect-uri: ${justauth.address}/social-callback?source=topiam
|
||||
scopes: [ openid, email, phone, profile ]
|
||||
qq:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=qq
|
||||
union-id: false
|
||||
weibo:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=weibo
|
||||
gitee:
|
||||
client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98
|
||||
client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac
|
||||
redirect-uri: ${justauth.address}/social-callback?source=gitee
|
||||
dingtalk:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=dingtalk
|
||||
baidu:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=baidu
|
||||
csdn:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=csdn
|
||||
coding:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=coding
|
||||
coding-group-name: xx
|
||||
oschina:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=oschina
|
||||
alipay_wallet:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
|
||||
alipay-public-key: MIIB**************DAQAB
|
||||
wechat_open:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=wechat_open
|
||||
wechat_mp:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=wechat_mp
|
||||
wechat_enterprise:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise
|
||||
agent-id: 1000002
|
||||
gitlab:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=gitlab
|
||||
# 和风天气 https://dev.qweather.com/
|
||||
weather:
|
||||
key-id: T65EAABUXC
|
||||
project-id: 2JTHPUQ5YY
|
||||
private-key: MC4CAQAwBQYDK2VwBCIEIMAglX7IsxYiTeM+FXXnvCUsIggajeP4s8gAllewm6BN
|
||||
api-host: n35rk53njv.re.qweatherapi.com
|
||||
# dxf转 geojson 执行文件名
|
||||
dxf2GeoJson:
|
||||
file-name: main
|
||||
ys7:
|
||||
app-key: xxx
|
||||
app-secret: xxx
|
||||
job:
|
||||
capture-enabled: false # 控制是否启用萤石抓拍任务
|
||||
device-sync-enabled: false # 控制是否同步萤石设备
|
||||
# 斯巴达算法
|
||||
sparta:
|
||||
url: http://119.3.204.120:8040
|
||||
client-id: test
|
||||
client-secret: 115fcb08fa6742a1b086d9bb80a6ad59
|
||||
# 身份证加密密钥(32 位)
|
||||
id-card:
|
||||
encrypt-key: 7ae260d150a14027d2238a1cf80a48ef
|
||||
recognizer:
|
||||
url: http://192.168.110.5:50071
|
||||
|
||||
qrCode:
|
||||
url: http://xny.yj-3d.com:7788
|
||||
# 无人机大图
|
||||
drone:
|
||||
url: http://192.168.110.2:9512
|
||||
# 聊天服务
|
||||
chat:
|
||||
server:
|
||||
port: 17077
|
||||
# rabbitmq 配置
|
||||
rabbitmq:
|
||||
exchange-name: menu-normal-exchange
|
||||
queue-name: menu-normal-queue
|
||||
routing-key: menu.normal.routing.key
|
||||
delay-exchange-name: menu-delay-queue
|
||||
delay-queue-name: menu-delay-exchange
|
||||
delay-routing-key: menu.delay.routing.key
|
||||
dead-letter-exchange: menu-dlx-exchange
|
||||
dead-letter-queue: menu-dlx-queue
|
||||
dead-letter-routing-key: menu.dlx.routing.key
|
||||
@ -1,3 +1,7 @@
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
port: 8899
|
||||
--- # 临时文件存储位置 避免临时文件被系统清理报错
|
||||
spring.servlet.multipart.location: /ruoyi/server/temp
|
||||
|
||||
@ -16,7 +20,7 @@ spring.boot.admin.client:
|
||||
|
||||
--- # snail-job 配置
|
||||
snail-job:
|
||||
enabled: false
|
||||
enabled: true
|
||||
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
|
||||
group: "ruoyi_group"
|
||||
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config`表
|
||||
@ -29,12 +33,18 @@ snail-job:
|
||||
# 随主应用端口漂移
|
||||
port: 2${server.port}
|
||||
# 客户端ip指定
|
||||
host:
|
||||
host: 127.0.0.1
|
||||
# RPC类型: netty, grpc
|
||||
rpc-type: grpc
|
||||
|
||||
--- # 数据源配置
|
||||
spring:
|
||||
ai:
|
||||
dashscope:
|
||||
api-key: sk-8d8df92fcbac4bd2922edba30b0bb8fa
|
||||
chat:
|
||||
options:
|
||||
model: qwen3-max
|
||||
datasource:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
|
||||
@ -60,6 +70,21 @@ spring:
|
||||
# lazy: true
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://192.168.110.2:13386/zmkgc?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# username: zmkgc
|
||||
# password: nWKDKRNRT48tFBdh
|
||||
# slave1:
|
||||
# lazy: true
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://192.168.110.2:13386/zmkgprod?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# username: zmkgprod
|
||||
# password: MaY8nehwWkJriWPm
|
||||
# # 从库数据源
|
||||
# slave:
|
||||
# lazy: true
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# username:
|
||||
# password:
|
||||
@ -164,7 +189,7 @@ sms:
|
||||
# 配置源类型用于标定配置来源(interface,yaml)
|
||||
config-type: yaml
|
||||
# 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制
|
||||
restricted: true
|
||||
restricted: false
|
||||
# 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效
|
||||
minute-max: 1
|
||||
# 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效
|
||||
@ -183,12 +208,45 @@ sms:
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
config2:
|
||||
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
|
||||
# 登录
|
||||
supplier: tencent
|
||||
access-key-id: 您的accessKey
|
||||
access-key-secret: 您的accessKeySecret
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2491779
|
||||
config3:
|
||||
# 注册
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2491776
|
||||
config4:
|
||||
# 质量工单逾期
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2534747
|
||||
config5:
|
||||
# 设计图纸
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2534750
|
||||
config6:
|
||||
# 安全工单
|
||||
supplier: tencent
|
||||
access-key-id: AKIDb3JK5dx4wa0DCxWqvxlKejvysZ3ITVJv
|
||||
access-key-secret: c5LPFsJI8k7GDxTkoeFj4A1ukQr66rPi
|
||||
signature: 重庆远界大数据研究院
|
||||
sdk-app-id: 1401018866
|
||||
template-id: 2534848
|
||||
|
||||
--- # 三方授权
|
||||
justauth:
|
||||
@ -273,10 +331,13 @@ weather:
|
||||
api-host: n35rk53njv.re.qweatherapi.com
|
||||
# dxf转 geojson 执行文件名
|
||||
dxf2GeoJson:
|
||||
file-name: main.exe
|
||||
file-name: main
|
||||
ys7:
|
||||
app-key: 3acf9f1a43dc4209841e0893003db0a2
|
||||
app-secret: 4bbf3e9394f55d3af6e3af27b2d3db36
|
||||
app-secret: 09e29c70ae1161fbc3ce2030fc09ba2e
|
||||
job:
|
||||
capture-enabled: true # 控制是否启用萤石抓拍任务
|
||||
device-sync-enabled: true # 控制是否同步萤石设备
|
||||
# 斯巴达算法
|
||||
sparta:
|
||||
url: http://119.3.204.120:8040
|
||||
@ -286,7 +347,25 @@ sparta:
|
||||
id-card:
|
||||
encrypt-key: 7ae260d150a14027d2238a1cf80a48ef
|
||||
recognizer:
|
||||
url: http://192.168.110.5:50070
|
||||
url: http://192.168.110.5:50071
|
||||
|
||||
qrCode:
|
||||
url: http://xny.yj-3d.com:7788
|
||||
# 无人机大图
|
||||
drone:
|
||||
url: http://192.168.110.2:9512
|
||||
# 聊天服务
|
||||
chat:
|
||||
server:
|
||||
port: 19099
|
||||
# rabbitmq 配置
|
||||
rabbitmq:
|
||||
exchange-name: prod-normal-exchange
|
||||
queue-name: prod-normal-queue
|
||||
routing-key: prod.normal.routing.key
|
||||
delay-exchange-name: prod-delay-queue
|
||||
delay-queue-name: prod-delay-exchange
|
||||
delay-routing-key: prod.delay.routing.key
|
||||
dead-letter-exchange: prod-dlx-exchange
|
||||
dead-letter-queue: prod-dlx-queue
|
||||
dead-letter-routing-key: prod.dlx.routing.key
|
||||
|
||||
@ -22,7 +22,7 @@ captcha:
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
port: 8899
|
||||
# port: 8899
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
@ -76,9 +76,9 @@ spring:
|
||||
servlet:
|
||||
multipart:
|
||||
# 单个文件大小
|
||||
max-file-size: 200MB
|
||||
max-file-size: 1024MB
|
||||
# 设置总上传的文件大小
|
||||
max-request-size: 200MB
|
||||
max-request-size: 1024MB
|
||||
mvc:
|
||||
# 设置静态资源路径 防止所有请求都去查静态资源
|
||||
static-path-pattern: /static/**
|
||||
@ -95,7 +95,13 @@ spring:
|
||||
deserialization:
|
||||
# 允许对象忽略json中不存在的属性
|
||||
fail_on_unknown_properties: false
|
||||
|
||||
rabbitmq:
|
||||
host: 192.168.110.2
|
||||
port: 5672
|
||||
username: admin
|
||||
password: yuanjiexny
|
||||
publisher-returns: true
|
||||
publisher-confirm-type: correlated
|
||||
# Sa-Token配置
|
||||
sa-token:
|
||||
# token名称 (同时也是cookie名称)
|
||||
@ -106,6 +112,12 @@ sa-token:
|
||||
is-share: false
|
||||
# jwt秘钥
|
||||
jwt-secret-key: abcdefghijklmnopqrstuvwxyz
|
||||
# token有效期,单位s 默认30天, -1代表永不过期
|
||||
timeout: 2592000
|
||||
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
|
||||
active-timeout: 2592000
|
||||
# 同一账号最大登录数量,-1代表不限
|
||||
max-login-count: -1
|
||||
|
||||
# security配置
|
||||
security:
|
||||
@ -122,11 +134,16 @@ security:
|
||||
- /warm-flow-ui/**
|
||||
- /warm-flow/**
|
||||
- /other/ys7Device/webhook
|
||||
# todo 仅测试
|
||||
- /facility/matrix/**
|
||||
- /**/changxie/callback/**
|
||||
- /progress/progressPlanDetail/insert/numberAI
|
||||
- /project/project/list/sub/matrix/**
|
||||
- /gps/equipment/dataAcceptance
|
||||
- /resource/oss/upload
|
||||
# todo 仅测试
|
||||
- /facility/matrix/**
|
||||
- /hat/device/data
|
||||
- /websocket/ue
|
||||
- /websocket/vehicle
|
||||
|
||||
# 多租户配置
|
||||
tenant:
|
||||
@ -165,7 +182,7 @@ mybatis-plus:
|
||||
# 数据加密
|
||||
mybatis-encryptor:
|
||||
# 是否开启加密
|
||||
enable: false
|
||||
enable: true
|
||||
# 默认加密算法
|
||||
algorithm: BASE64
|
||||
# 编码方式 BASE64/HEX。默认BASE64
|
||||
@ -199,6 +216,10 @@ api-decrypt:
|
||||
- /gps/equipment/dataAcceptance # GPS数据接收接口
|
||||
|
||||
springdoc:
|
||||
default-flat-param-object: true
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
tags-sorter: alpha
|
||||
api-docs:
|
||||
# 是否开启接口文档
|
||||
enabled: true
|
||||
@ -218,10 +239,14 @@ springdoc:
|
||||
components:
|
||||
# 鉴权方式配置
|
||||
security-schemes:
|
||||
apiKey:
|
||||
type: APIKEY
|
||||
Authorization:
|
||||
type: HTTP
|
||||
in: HEADER
|
||||
name: ${sa-token.token-name}
|
||||
Clientid:
|
||||
type: HTTP
|
||||
in: HEADER
|
||||
name: Clientid
|
||||
#这里定义了两个分组,可定义多个,也可以不定义
|
||||
group-configs:
|
||||
- group: 1.通用模块
|
||||
@ -250,12 +275,10 @@ springdoc:
|
||||
packages-to-scan: org.dromara.design
|
||||
- group: 13.工作流模块
|
||||
packages-to-scan: org.dromara.workflow
|
||||
- group: 14.罗成模块
|
||||
packages-to-scan: org.dromara.cory
|
||||
- group: 14.合同模块
|
||||
packages-to-scan: org.dromara.ctr
|
||||
- group: 15.无人机模块
|
||||
packages-to-scan: org.dromara.drone
|
||||
- group: 20.代码生成模块
|
||||
packages-to-scan: org.dromara.generator
|
||||
- group: 16.app模块
|
||||
packages-to-scan: org.dromara.app
|
||||
- group: 17.材料设备模块
|
||||
@ -264,23 +287,36 @@ springdoc:
|
||||
packages-to-scan: org.dromara.out
|
||||
- group: 19.消息模块
|
||||
packages-to-scan: org.dromara.message
|
||||
# - group: 20.手续模块
|
||||
# packages-to-scan: org.dromara.formalities
|
||||
- group: 20.代码生成模块
|
||||
packages-to-scan: org.dromara.generator
|
||||
- group: 21.分标策划模块
|
||||
packages-to-scan: org.dromara.tender
|
||||
- group: 22.大屏模块
|
||||
packages-to-scan: org.dromara.bigscreen
|
||||
- group: 22.投标管理模块
|
||||
packages-to-scan: org.dromara.bidding
|
||||
- group: 23.GPS定位模块
|
||||
packages-to-scan: org.dromara.gps
|
||||
|
||||
# - group: 20.合同模块
|
||||
# packages-to-scan: org.dromara.ctr
|
||||
- group: 24.招标模块
|
||||
packages-to-scan: org.dromara.tender
|
||||
|
||||
|
||||
- group: 25.数据迁移模块
|
||||
packages-to-scan: org.dromara.transferData
|
||||
- group: 26.netty消息模块
|
||||
packages-to-scan: org.dromara.websocket
|
||||
- group: 27.新中大模块
|
||||
packages-to-scan: org.dromara.xzd
|
||||
- group: 28.车辆模块
|
||||
packages-to-scan: org.dromara.vehicle
|
||||
- group: 29.app版本模块
|
||||
packages-to-scan: org.dromara.app
|
||||
- group: 30.AI 模块
|
||||
packages-to-scan: org.dromara.ai
|
||||
- group: 31.投标管理模块
|
||||
packages-to-scan: org.dromara.bidding
|
||||
- group: 32.设备模块
|
||||
packages-to-scan: org.dromara.device
|
||||
- group: 33.摄像头模块
|
||||
packages-to-scan: org.dromara.other
|
||||
- group: 34.机械模块
|
||||
packages-to-scan: org.dromara.mechanical
|
||||
# knife4j的增强配置,不需要增强可以不配
|
||||
knife4j:
|
||||
enable: true
|
||||
@ -295,6 +331,18 @@ xss:
|
||||
excludeUrls:
|
||||
- /system/notice
|
||||
- /warm-flow/save-xml
|
||||
- /project/project
|
||||
- /xzd/contractDetails/**
|
||||
- /xzd/contractChange/**
|
||||
- /xzd/comprehensive/csContractChange/**
|
||||
- /xzd/comprehensive/csContractInformation/**
|
||||
- /xzd/hetongbiangeng/**
|
||||
- /xzd/fenbaohetongjungong/**
|
||||
- /xzd/fenbaohetongbiangg/**
|
||||
- /xzd/fenbaohetongxinxi/**
|
||||
- /xzd/contractManagement/**
|
||||
- /xzd/jixiehetongxinxi/contractMachinery/**
|
||||
- /xzd/jixiehetongbiang/machineryContractAlteration/**
|
||||
|
||||
# 全局线程池相关配置
|
||||
# 如使用JDK21请直接使用虚拟线程 不要开启此配置
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
xinnengyuan/ruoyi-admin/src/main/resources/template/设计更改通知单.docx
Normal file
BIN
xinnengyuan/ruoyi-admin/src/main/resources/template/设计更改通知单.docx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
xinnengyuan/ruoyi-admin/src/main/resources/template/设计验证表.docx
Normal file
BIN
xinnengyuan/ruoyi-admin/src/main/resources/template/设计验证表.docx
Normal file
Binary file not shown.
@ -1,18 +1,49 @@
|
||||
package org.dromara.test;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.IdcardUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.constant.HttpStatus;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.utils.IdCardEncryptorUtil;
|
||||
import org.dromara.contractor.domain.SubConstructionUser;
|
||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||
import org.dromara.contractor.service.ISubUserSalaryDetailService;
|
||||
import org.dromara.design.service.IDesTechnicalStandardService;
|
||||
import org.dromara.facility.domain.FacMatrix;
|
||||
import org.dromara.facility.service.IFacMatrixService;
|
||||
import org.dromara.facility.service.IFacPhotovoltaicPanelPartsService;
|
||||
import org.dromara.facility.service.IFacPhotovoltaicPanelService;
|
||||
import org.dromara.manager.recognizermanager.enums.RecognizerTypeEnum;
|
||||
import org.dromara.manager.recognizermanager.vo.RecognizeConvertCoordinateResult;
|
||||
import org.dromara.manager.ys7manager.Ys7Constant;
|
||||
import org.dromara.manager.ys7manager.Ys7Manager;
|
||||
import org.dromara.manager.ys7manager.vo.Ys7ResponseVo;
|
||||
import org.dromara.other.domain.OthYs7Device;
|
||||
import org.dromara.other.domain.dto.ys7deviceimg.OthYs7DeviceImgCreateByCapture;
|
||||
import org.dromara.other.service.IOthYs7DeviceImgService;
|
||||
import org.dromara.other.service.IOthYs7DeviceService;
|
||||
import org.dromara.out.domain.OutConstructionValue;
|
||||
import org.dromara.out.domain.OutConstructionValueRange;
|
||||
import org.dromara.out.service.IOutConstructionValueRangeService;
|
||||
import org.dromara.out.service.IOutConstructionValueService;
|
||||
import org.dromara.progress.domain.PgsProgressCategory;
|
||||
import org.dromara.progress.domain.PgsProgressPlanDetail;
|
||||
import org.dromara.progress.domain.dto.progresscategory.PgsProgressCategoryCreateReq;
|
||||
import org.dromara.progress.service.IPgsProgressCategoryService;
|
||||
import org.dromara.progress.service.IPgsProgressCategoryTemplateService;
|
||||
import org.dromara.progress.service.IPgsProgressPlanDetailService;
|
||||
import org.dromara.project.domain.BusProject;
|
||||
import org.dromara.project.service.IBusProjectService;
|
||||
import org.dromara.system.service.ISysDeptService;
|
||||
import org.dromara.tender.service.impl.TenderSupplierInputServiceImpl;
|
||||
@ -20,8 +51,16 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
@ -40,6 +79,9 @@ public class DemoTest {
|
||||
@Resource
|
||||
private IPgsProgressCategoryTemplateService progressCategoryTemplateService;
|
||||
|
||||
@Resource
|
||||
private IPgsProgressPlanDetailService progressPlanDetailService;
|
||||
|
||||
@Resource
|
||||
private IFacMatrixService matrixService;
|
||||
|
||||
@ -57,6 +99,231 @@ public class DemoTest {
|
||||
@Resource
|
||||
private ISubConstructionUserService constructionUserService;
|
||||
|
||||
@Resource
|
||||
private ISubUserSalaryDetailService userSalaryDetailService;
|
||||
|
||||
@Resource
|
||||
private IdCardEncryptorUtil idCardEncryptorUtil;
|
||||
|
||||
@Resource
|
||||
private IFacPhotovoltaicPanelService photovoltaicPanelService;
|
||||
|
||||
@Resource
|
||||
private IOutConstructionValueRangeService constructionValueRangeService;
|
||||
|
||||
@Resource
|
||||
private IOutConstructionValueService constructionValueService;
|
||||
|
||||
@Resource
|
||||
private IOthYs7DeviceService ys7DeviceService;
|
||||
|
||||
@Resource
|
||||
private IOthYs7DeviceImgService ys7DeviceImgService;
|
||||
|
||||
@Resource
|
||||
private Ys7Manager ys7Manager;
|
||||
|
||||
|
||||
@Test
|
||||
void testConstructionValue() {
|
||||
/* LocalDate today = LocalDate.now();
|
||||
// 找到本周一
|
||||
LocalDate thisMonday = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
|
||||
// 上周一 = 本周一 - 1 周
|
||||
LocalDate lastMonday = thisMonday.minusWeeks(1);
|
||||
// 上周日 = 上周一 + 6 天
|
||||
LocalDate lastSunday = lastMonday.plusDays(6);
|
||||
log.info("执行定时任务:同步 {}至{} 计划详情到施工产值", lastMonday, lastSunday);
|
||||
Boolean synced = progressPlanDetailService.syncPlanDetail2ConstructionValue(lastMonday, lastSunday, null);*/
|
||||
LocalDate start = LocalDate.of(2024, 1, 1); // 起始时间(2024-01-01)
|
||||
LocalDate end = LocalDate.of(2025, 10, 27); // 截止时间(2025-09-15)
|
||||
|
||||
// 如果起始不是周一,调整到当周的周一
|
||||
if (start.getDayOfWeek() != DayOfWeek.MONDAY) {
|
||||
start = start.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
|
||||
}
|
||||
|
||||
while (!start.isAfter(end)) {
|
||||
LocalDate monday = start;
|
||||
LocalDate sunday = start.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
|
||||
|
||||
// 输出本周的周一和周日
|
||||
System.out.println(monday + " ~ " + sunday);
|
||||
log.info("执行定时任务:同步 {}至{} 计划详情到施工产值", monday, sunday);
|
||||
// Boolean synced = progressPlanDetailService.syncPlanDetail2ConstructionValue(start, now, null);
|
||||
List<BusProject> list = projectService.lambdaQuery()
|
||||
.eq(BusProject::getPId, 1968506669544656898L)
|
||||
.list();
|
||||
List<Long> list1 = new ArrayList<>(list.stream().map(BusProject::getId).toList());
|
||||
list1.add(1968506669544656898L);
|
||||
// 获取范围时间内的计划详情
|
||||
List<PgsProgressPlanDetail> planDetailList = progressPlanDetailService.lambdaQuery()
|
||||
.in(PgsProgressPlanDetail::getProjectId, list1)
|
||||
.ge(PgsProgressPlanDetail::getDate, monday)
|
||||
.le(PgsProgressPlanDetail::getDate, sunday)
|
||||
.ne(PgsProgressPlanDetail::getFinishedNumber, BigDecimal.ZERO)
|
||||
.eq(PgsProgressPlanDetail::getStatus, "1")
|
||||
.list();
|
||||
if (CollUtil.isEmpty(planDetailList)) {
|
||||
// 下一周
|
||||
start = start.plusWeeks(1);
|
||||
continue;
|
||||
}
|
||||
// 获取进度类别
|
||||
List<Long> categoryIds = planDetailList.stream()
|
||||
.map(PgsProgressPlanDetail::getProgressCategoryId)
|
||||
.distinct()
|
||||
.toList();
|
||||
List<PgsProgressCategory> categoryList = progressCategoryService.lambdaQuery()
|
||||
.in(CollUtil.isNotEmpty(categoryIds), PgsProgressCategory::getId, categoryIds)
|
||||
.list();
|
||||
Map<Long, PgsProgressCategory> categoryMap = categoryList.stream()
|
||||
.collect(
|
||||
Collectors.toMap(PgsProgressCategory::getId,
|
||||
Function.identity(),
|
||||
(v1, v2) -> v1)
|
||||
);
|
||||
// 为每一个项目创建一个施工产值范围
|
||||
Set<Long> projectIds = planDetailList.stream()
|
||||
.map(PgsProgressPlanDetail::getProjectId).collect(Collectors.toSet());
|
||||
// 获取所有父级项目
|
||||
List<BusProject> projectList = projectService.listByIds(projectIds);
|
||||
List<Long> allProjectIds = projectList
|
||||
.stream().map(project -> {
|
||||
if (project.getPId() != 0L) {
|
||||
return project.getPId();
|
||||
} else {
|
||||
return project.getId();
|
||||
}
|
||||
}).distinct().toList();
|
||||
// 根据项目区分
|
||||
Map<Long, List<PgsProgressPlanDetail>> detailMap = planDetailList.stream()
|
||||
.collect(Collectors.groupingBy(PgsProgressPlanDetail::getProjectId));
|
||||
List<OutConstructionValue> saveList = new ArrayList<>();
|
||||
List<PgsProgressPlanDetail> allUpdateList = new ArrayList<>();
|
||||
List<OutConstructionValueRange> ranges = allProjectIds.stream().map(id -> {
|
||||
OutConstructionValueRange range = new OutConstructionValueRange();
|
||||
long rangeId = IdWorker.getId(range);
|
||||
range.setId(rangeId);
|
||||
range.setProjectId(id);
|
||||
range.setStartDate(monday);
|
||||
range.setEndDate(sunday);
|
||||
// 获取所有子项目
|
||||
List<Long> subProject = new ArrayList<>(projectList.stream()
|
||||
.filter(project -> Objects.equals(project.getPId(), id))
|
||||
.map(BusProject::getId)
|
||||
.distinct()
|
||||
.toList());
|
||||
subProject.add(id);
|
||||
List<PgsProgressPlanDetail> detailList = new ArrayList<>();
|
||||
for (Long p : subProject) {
|
||||
List<PgsProgressPlanDetail> details = detailMap.getOrDefault(p, List.of());
|
||||
detailList.addAll(details);
|
||||
}
|
||||
if (CollUtil.isEmpty(detailList)) {
|
||||
return null;
|
||||
}
|
||||
BigDecimal allConstructionValue = BigDecimal.ZERO;
|
||||
BigDecimal allOwnerValue = BigDecimal.ZERO;
|
||||
List<PgsProgressPlanDetail> updateList = new ArrayList<>();
|
||||
for (PgsProgressPlanDetail planDetail : detailList) {
|
||||
OutConstructionValue value = new OutConstructionValue();
|
||||
Long progressCategoryId = planDetail.getProgressCategoryId();
|
||||
PgsProgressCategory category = categoryMap.get(progressCategoryId);
|
||||
if (category == null) {
|
||||
continue;
|
||||
}
|
||||
value.setProjectId(id);
|
||||
value.setRangeId(rangeId);
|
||||
value.setMatrixId(category.getMatrixId());
|
||||
value.setProgressCategoryId(progressCategoryId);
|
||||
value.setDetailId(planDetail.getId());
|
||||
BigDecimal finishedNumber = planDetail.getFinishedNumber();
|
||||
BigDecimal aiFill = planDetail.getAiFill();
|
||||
// 如果完成数量为0, 则不保存
|
||||
if (finishedNumber.compareTo(BigDecimal.ZERO) == 0) {
|
||||
continue;
|
||||
}
|
||||
value.setArtificialNum(finishedNumber.subtract(aiFill).intValue());
|
||||
value.setUavNum(aiFill.intValue());
|
||||
value.setPlanNum(planDetail.getPlanNumber().intValue());
|
||||
value.setReportDate(planDetail.getDate());
|
||||
value.setPlanDate(planDetail.getDate());
|
||||
// 计算产值
|
||||
BigDecimal constructionPrice = category.getConstructionPrice();
|
||||
BigDecimal ownerPrice = category.getOwnerPrice();
|
||||
BigDecimal constructionValue = constructionPrice.multiply(finishedNumber).setScale(4, RoundingMode.HALF_UP);
|
||||
BigDecimal ownerValue = ownerPrice.multiply(finishedNumber).setScale(4, RoundingMode.HALF_UP);
|
||||
value.setOutValue(constructionValue);
|
||||
value.setOwnerValue(ownerValue);
|
||||
// 统计总产值
|
||||
allConstructionValue = allConstructionValue.add(constructionValue);
|
||||
allOwnerValue = allOwnerValue.add(ownerValue);
|
||||
// 添加需要修改状态的计划详情
|
||||
PgsProgressPlanDetail update = new PgsProgressPlanDetail();
|
||||
update.setId(planDetail.getId());
|
||||
update.setStatus("2");
|
||||
updateList.add(update);
|
||||
saveList.add(value);
|
||||
}
|
||||
range.setOutValue(allConstructionValue.setScale(4, RoundingMode.HALF_UP));
|
||||
range.setOwnerValue(allOwnerValue.setScale(4, RoundingMode.HALF_UP));
|
||||
// 如果产值都为0,则不保存
|
||||
if (allConstructionValue.compareTo(BigDecimal.ZERO) == 0 && allOwnerValue.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return null;
|
||||
}
|
||||
allUpdateList.addAll(updateList);
|
||||
return range;
|
||||
}).filter(Objects::nonNull).toList();
|
||||
// 保存数据
|
||||
if (CollUtil.isNotEmpty(ranges)) {
|
||||
boolean saveBatch = constructionValueRangeService.saveBatch(ranges);
|
||||
if (!saveBatch) {
|
||||
throw new ServiceException("同步计划详情到施工产值失败,数据库异常", HttpStatus.ERROR);
|
||||
}
|
||||
}
|
||||
if (CollUtil.isNotEmpty(saveList)) {
|
||||
boolean saved = constructionValueService.saveBatch(saveList);
|
||||
if (!saved) {
|
||||
throw new ServiceException("同步计划详情到施工产值失败,数据库异常", HttpStatus.ERROR);
|
||||
}
|
||||
}
|
||||
if (CollUtil.isNotEmpty(allUpdateList)) {
|
||||
boolean updateBatch = progressPlanDetailService.updateBatchById(allUpdateList);
|
||||
if (!updateBatch) {
|
||||
throw new ServiceException("同步计划详情到施工产值失败,数据库异常", HttpStatus.ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
// 下一周
|
||||
start = start.plusWeeks(1);
|
||||
}
|
||||
/* LocalDate today = LocalDate.of(2025, 9, 16);
|
||||
LocalDate localDate = today.minusDays(1);*/
|
||||
/* // 找到本周一
|
||||
LocalDate thisMonday = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
|
||||
// 上周一 = 本周一 - 1 周
|
||||
LocalDate lastMonday = thisMonday.minusWeeks(1);
|
||||
// 上周日 = 上周一 + 6 天
|
||||
LocalDate lastSunday = lastMonday.plusDays(6);*/
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void setIdCardEncryptorUtil() {
|
||||
List<SubConstructionUser> list = constructionUserService.list();
|
||||
List<SubConstructionUser> update = list.stream().filter(user -> StringUtils.isNotBlank(user.getSfzNumber()))
|
||||
.filter(user -> IdcardUtil.isValidCard18(user.getSfzNumber()))
|
||||
.toList().stream()
|
||||
.map(user -> {
|
||||
SubConstructionUser updateUser = new SubConstructionUser();
|
||||
updateUser.setId(user.getId());
|
||||
updateUser.setSfzNumber(idCardEncryptorUtil.encrypt(user.getSfzNumber()));
|
||||
return updateUser;
|
||||
}).toList();
|
||||
constructionUserService.updateBatchById(update);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
Boolean result = photovoltaicPanelPartsService
|
||||
@ -120,4 +387,213 @@ public class DemoTest {
|
||||
constructionUserService.update(constructionUserLuw);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSalary() {
|
||||
List<SubConstructionUser> list = constructionUserService.lambdaQuery()
|
||||
.eq(SubConstructionUser::getProjectId, 1897160897167638529L)
|
||||
.list();
|
||||
if (CollUtil.isNotEmpty(list)) {
|
||||
for (SubConstructionUser user : list) {
|
||||
for (int i = 1; i < 7; i++) {
|
||||
userSalaryDetailService.insertByAttendance(user.getSysUserId(), LocalDate.now().minusDays(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testImport() {
|
||||
photovoltaicPanelService.updateFinishNumberByCoordinate(List.of(1968510961294921730L),
|
||||
List.of(new RecognizeConvertCoordinateResult("107.124334530", "23.830557974")),
|
||||
RecognizerTypeEnum.BRACKET.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemove() {
|
||||
List<Long> projectIds = List.of(
|
||||
1968510961294921730L,
|
||||
1968511074637598722L,
|
||||
1968511192354934785L,
|
||||
1968514103508094977L,
|
||||
1968514270462365697L,
|
||||
1968515178185248770L,
|
||||
1968515232044306434L,
|
||||
1968515275153362945L,
|
||||
1968515323392053250L,
|
||||
1968515384058466306L,
|
||||
1968515428077686785L,
|
||||
1968515467575447554L,
|
||||
1968515518674653186L
|
||||
);
|
||||
for (Long projectId : projectIds) {
|
||||
PgsProgressCategory p = progressCategoryService.lambdaQuery()
|
||||
.eq(PgsProgressCategory::getProjectId, projectId)
|
||||
.eq(PgsProgressCategory::getMatrixId, "0")
|
||||
.eq(PgsProgressCategory::getName, "地埋电缆")
|
||||
.one();
|
||||
progressCategoryService.remove(new LambdaQueryWrapper<>(PgsProgressCategory.class)
|
||||
.eq(PgsProgressCategory::getParentId, p.getId()));
|
||||
progressCategoryService.removeById(p);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProject1() {
|
||||
List<BusProject> projects = projectService.lambdaQuery()
|
||||
.eq(BusProject::getPId, 1897160897167638529L)
|
||||
.list();
|
||||
List<Long> projectIds = projects.stream().map(BusProject::getId).toList();
|
||||
List<PgsProgressCategory> t10101 = progressCategoryService.lambdaQuery()
|
||||
.in(PgsProgressCategory::getProjectId, projectIds)
|
||||
.ne(PgsProgressCategory::getMatrixId, "0")
|
||||
.eq(PgsProgressCategory::getMatrixName, "T10101")
|
||||
.list();
|
||||
log.info("t10101: {}", t10101);
|
||||
Map<String, PgsProgressCategory> name = t10101.stream()
|
||||
.collect(Collectors.toMap(PgsProgressCategory::getName, Function.identity()));
|
||||
List<PgsProgressCategory> other = progressCategoryService.lambdaQuery()
|
||||
.in(PgsProgressCategory::getProjectId, projectIds)
|
||||
.ne(PgsProgressCategory::getMatrixId, "0")
|
||||
.ne(PgsProgressCategory::getMatrixName, "T10101")
|
||||
.ne(PgsProgressCategory::getParentId, 0)
|
||||
.list();
|
||||
log.info("other: {}", other);
|
||||
other.forEach(o -> {
|
||||
o.setId(o.getId());
|
||||
PgsProgressCategory category = name.get(o.getName());
|
||||
if (StringUtils.isBlank(o.getWorkType())) {
|
||||
o.setTotal(category.getTotal());
|
||||
}
|
||||
o.setUnit(category.getUnit());
|
||||
o.setUnitType(category.getUnitType());
|
||||
BigDecimal ownerPrice = category.getOwnerPrice();
|
||||
o.setOwnerPrice(ownerPrice);
|
||||
BigDecimal constructionPrice = category.getConstructionPrice();
|
||||
o.setConstructionPrice(constructionPrice);
|
||||
// 计算产值
|
||||
try {
|
||||
BigDecimal multiply = o.getOwnerPrice().multiply(o.getTotal());
|
||||
o.setOwnerOutputValue(multiply.setScale(4, RoundingMode.HALF_UP));
|
||||
BigDecimal multiply1 = o.getConstructionPrice().multiply(o.getTotal());
|
||||
o.setConstructionOutputValue(multiply1.setScale(4, RoundingMode.HALF_UP));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
});
|
||||
progressCategoryService.updateBatchById(other);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProject2() {
|
||||
PgsProgressCategory p1 = progressCategoryService.lambdaQuery()
|
||||
.eq(PgsProgressCategory::getProjectId, 1968510858786131970L)
|
||||
.eq(PgsProgressCategory::getMatrixId, "0")
|
||||
.eq(PgsProgressCategory::getName, "其他工程")
|
||||
.one();
|
||||
List<PgsProgressCategory> progressCategoryList = progressCategoryService.lambdaQuery()
|
||||
.eq(PgsProgressCategory::getProjectId, 1968510858786131970L)
|
||||
.eq(PgsProgressCategory::getMatrixId, "0")
|
||||
.eq(PgsProgressCategory::getParentId, p1.getId())
|
||||
.list();
|
||||
List<Long> projectIds = List.of(
|
||||
1968510961294921730L,
|
||||
1968511074637598722L,
|
||||
1968511192354934785L,
|
||||
1968514103508094977L,
|
||||
1968514270462365697L,
|
||||
1968515178185248770L,
|
||||
1968515232044306434L,
|
||||
1968515275153362945L,
|
||||
1968515323392053250L,
|
||||
1968515384058466306L,
|
||||
1968515428077686785L,
|
||||
1968515467575447554L,
|
||||
1968515518674653186L
|
||||
);
|
||||
for (Long projectId : projectIds) {
|
||||
PgsProgressCategory p = progressCategoryService.lambdaQuery()
|
||||
.eq(PgsProgressCategory::getProjectId, projectId)
|
||||
.eq(PgsProgressCategory::getMatrixId, "0")
|
||||
.eq(PgsProgressCategory::getName, "其他工程")
|
||||
.one();
|
||||
progressCategoryService.remove(new LambdaQueryWrapper<>(PgsProgressCategory.class)
|
||||
.eq(PgsProgressCategory::getParentId, p.getId()));
|
||||
for (PgsProgressCategory category : progressCategoryList) {
|
||||
PgsProgressCategoryCreateReq req = new PgsProgressCategoryCreateReq();
|
||||
req.setParentId(p.getId());
|
||||
req.setProjectId(p.getProjectId());
|
||||
req.setMatrixId(p.getMatrixId());
|
||||
req.setName(category.getName());
|
||||
req.setUnitType(category.getUnitType());
|
||||
req.setUnit(category.getUnit());
|
||||
req.setOwnerPrice(category.getOwnerPrice());
|
||||
req.setConstructionPrice(category.getConstructionPrice());
|
||||
req.setTotal(category.getTotal());
|
||||
|
||||
progressCategoryService.insertByReq(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void openSxtLx() {
|
||||
String token = ys7Manager.getToken();
|
||||
List<OthYs7Device> list = ys7DeviceService.list();
|
||||
for (OthYs7Device othYs7Device : list) {
|
||||
HashMap<String, Object> paramMap = new HashMap<>();
|
||||
paramMap.put("accessToken", token);
|
||||
paramMap.put("deviceSerial", othYs7Device.getDeviceSerial());
|
||||
paramMap.put("enable", 1);
|
||||
String errorMsg = "Ys7 Token 请求失败";
|
||||
try (HttpResponse response = HttpRequest.post(Ys7Constant.setDeviceVideoUrlByPost)
|
||||
.form(paramMap)
|
||||
.execute()) {
|
||||
if (!response.isOk()) {
|
||||
log.error("{}:{}", errorMsg, response.getStatus());
|
||||
// throw new ServiceException(errorMsg + response.getStatus());
|
||||
}
|
||||
String body = response.body();
|
||||
Ys7ResponseVo responseVo = JSONUtil.toBean(body, Ys7ResponseVo.class);
|
||||
if (!responseVo.getCode().equals("200")) {
|
||||
log.error("{},状态码:{},{}", errorMsg, responseVo.getCode(), responseVo.getMsg());
|
||||
// throw new ServiceException(errorMsg + responseVo.getMsg());
|
||||
}
|
||||
log.info("Ys7 Token 请求成功:{}", body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void capturePic() {
|
||||
List<String> urlList = List.of(
|
||||
"http://xny.yj-3d.com:9000/xinnengyuan-dev/2025/10/23/db8a379b456142459852b654f20d5f97.png",
|
||||
"http://xny.yj-3d.com:9000/xinnengyuan-dev/2025/10/23/913dbcf0f7244c8b878e84b5525bec4b.png",
|
||||
"http://xny.yj-3d.com:9000/xinnengyuan-dev/2025/10/23/2b904765f03f40b2ad0ddbf6ddeadb45.png"
|
||||
);
|
||||
for (String url : urlList) {
|
||||
Pattern pattern = Pattern.compile(".*/device/img/([^/]+)/.*");
|
||||
Matcher matcher = pattern.matcher(url);
|
||||
OthYs7Device ys7Device = ys7DeviceService.lambdaQuery()
|
||||
.eq(OthYs7Device::getDeviceSerial, "GA1730672")
|
||||
.last("limit 1")
|
||||
.one();
|
||||
if (ys7Device == null) {
|
||||
throw new ServiceException("设备不存在", HttpStatus.ERROR);
|
||||
}
|
||||
String deviceSerial = ys7Device.getDeviceSerial();
|
||||
// 如果没有预置位,则直接对默认通道抓图
|
||||
OthYs7DeviceImgCreateByCapture img = new OthYs7DeviceImgCreateByCapture();
|
||||
img.setProjectId(ys7Device.getProjectId());
|
||||
img.setDeviceSerial(deviceSerial);
|
||||
img.setDeviceName(ys7Device.getDeviceName());
|
||||
// String url = "http://xny.yj-3d.com:9000/xinnengyuan/ys7/device/img/GA1044315/2025-10-13_859fdfb7dde540608356f29cb9e3d63e.jpg";
|
||||
// String url = "http://xny.yj-3d.com:9000/xinnengyuan/ys7/device/img/GA1044315/2025-10-12_2801707255b84004acb5fee2a75299b2.jpg";
|
||||
img.setCreateTime(new Date());
|
||||
img.setUrl(url);
|
||||
log.info("图片:{},识别中", url);
|
||||
ys7DeviceImgService.saveCapturePic(List.of(img));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,132 @@
|
||||
package org.dromara.test;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.materials.domain.MatMaterialIssue;
|
||||
import org.dromara.materials.domain.MatMaterialReceive;
|
||||
import org.dromara.materials.domain.MatMaterials;
|
||||
import org.dromara.materials.domain.MatMaterialsInventory;
|
||||
import org.dromara.materials.domain.enums.MatMaterialsInventoryOutPutEnum;
|
||||
import org.dromara.materials.service.IMatMaterialIssueService;
|
||||
import org.dromara.materials.service.IMatMaterialReceiveService;
|
||||
import org.dromara.materials.service.IMatMaterialsInventoryService;
|
||||
import org.dromara.materials.service.IMatMaterialsService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-10-30 15:58
|
||||
*/
|
||||
@Slf4j
|
||||
@SpringBootTest
|
||||
public class MaterialsTest {
|
||||
|
||||
/**
|
||||
* 长顺项目id
|
||||
*/
|
||||
private static final Long PROJECT_ID = 1897161054676336641L;
|
||||
|
||||
@Resource
|
||||
private IMatMaterialsService materialsService;
|
||||
|
||||
@Resource
|
||||
private IMatMaterialsInventoryService materialsInventoryService;
|
||||
|
||||
@Resource
|
||||
private IMatMaterialReceiveService materialReceiveService;
|
||||
|
||||
@Resource
|
||||
private IMatMaterialIssueService materialIssueService;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// 获取所有材料
|
||||
List<MatMaterials> materials = materialsService.lambdaQuery()
|
||||
.eq(MatMaterials::getProjectId, PROJECT_ID)
|
||||
.list();
|
||||
Set<Long> materialIds = materials.stream().map(MatMaterials::getId).collect(Collectors.toSet());
|
||||
// 获取所有材料的出库数据
|
||||
List<MatMaterialsInventory> inventoryList = materialsInventoryService.lambdaQuery()
|
||||
.in(MatMaterialsInventory::getMaterialsId, materialIds)
|
||||
.eq(MatMaterialsInventory::getOutPut, MatMaterialsInventoryOutPutEnum.OUT.getValue())
|
||||
.list();
|
||||
// 按表单编号分组
|
||||
Map<String, List<MatMaterials>> map = materials.stream()
|
||||
.collect(Collectors.groupingBy(MatMaterials::getFormCode));
|
||||
for (Map.Entry<String, List<MatMaterials>> entry : map.entrySet()) {
|
||||
String formCode = entry.getKey();
|
||||
List<MatMaterials> materialsList = entry.getValue();
|
||||
// 获取入库数据
|
||||
MatMaterialReceive receive = materialReceiveService.lambdaQuery()
|
||||
.eq(MatMaterialReceive::getFormCode, formCode)
|
||||
.one();
|
||||
// 创建领料出库数据
|
||||
MatMaterialIssue issue = new MatMaterialIssue();
|
||||
issue.setProjectId(PROJECT_ID);
|
||||
issue.setMaterialSource("2");
|
||||
issue.setFormCode(formCode);
|
||||
issue.setProjectName(receive.getProjectName());
|
||||
issue.setMaterialName(receive.getMaterialName());
|
||||
issue.setOrderingUnit(receive.getOrderingUnit());
|
||||
issue.setSupplierUnit(receive.getSupplierUnit());
|
||||
// issue.setIssueUnit(inventory.getRecipient());
|
||||
// issue.setIssueUnitId(inventory.getRecipientId());
|
||||
// issue.setShipper(inventory.getShipper());
|
||||
|
||||
|
||||
|
||||
|
||||
// issue.setStorageUnit();
|
||||
issue.setCertCount(0);
|
||||
issue.setReportCount(0);
|
||||
issue.setTechDocCount(0);
|
||||
issue.setLicenseCount(0);
|
||||
log.info("领料出库数据:{}", issue);
|
||||
log.info("=============================");
|
||||
}
|
||||
|
||||
for (MatMaterials material : materials) {
|
||||
String formCode = material.getFormCode();
|
||||
// 查看入库数据
|
||||
MatMaterialReceive receive = materialReceiveService.lambdaQuery()
|
||||
.eq(MatMaterialReceive::getFormCode, formCode)
|
||||
.one();
|
||||
// 查看出库数据
|
||||
/* List<MatMaterialsInventory> inventoryList = materialsInventoryService.lambdaQuery()
|
||||
.eq(MatMaterialsInventory::getMaterialsId, material.getId())
|
||||
.eq(MatMaterialsInventory::getOutPut, MatMaterialsInventoryOutPutEnum.OUT.getValue())
|
||||
.list();
|
||||
if (CollUtil.isEmpty(inventoryList)) {
|
||||
continue;
|
||||
}*/
|
||||
// 创建领料出库数据
|
||||
List<MatMaterialIssue> issueList = inventoryList.stream().map(inventory -> {
|
||||
MatMaterialIssue issue = new MatMaterialIssue();
|
||||
issue.setProjectId(PROJECT_ID);
|
||||
issue.setMaterialSource("2");
|
||||
issue.setFormCode(receive.getFormCode());
|
||||
issue.setProjectName(receive.getProjectName());
|
||||
issue.setMaterialName(receive.getMaterialName());
|
||||
issue.setOrderingUnit(receive.getOrderingUnit());
|
||||
issue.setSupplierUnit(receive.getSupplierUnit());
|
||||
issue.setIssueUnit(inventory.getRecipient());
|
||||
issue.setIssueUnitId(inventory.getRecipientId());
|
||||
issue.setShipper(inventory.getShipper());
|
||||
// issue.setStorageUnit();
|
||||
issue.setCertCount(0);
|
||||
issue.setReportCount(0);
|
||||
issue.setTechDocCount(0);
|
||||
issue.setLicenseCount(0);
|
||||
log.info("领料出库数据:{}", issue);
|
||||
log.info("=============================");
|
||||
return issue;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,23 @@
|
||||
package org.dromara.test;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.manager.recognizermanager.RecognizerManager;
|
||||
import org.dromara.manager.recognizermanager.enums.RecognizerTypeEnum;
|
||||
import org.dromara.manager.recognizermanager.vo.RecognizeVo;
|
||||
import org.dromara.progress.domain.PgsProgressPlanDetail;
|
||||
import org.dromara.progress.domain.vo.progressplandetail.PgsProgressPlanDetailFinishedVo;
|
||||
import org.dromara.progress.service.IPgsProgressPlanDetailService;
|
||||
import org.dromara.progress.service.IPgsProgressPlanService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
@ -21,9 +30,55 @@ public class RecognizerTest {
|
||||
@Resource
|
||||
private RecognizerManager recognizerManager;
|
||||
|
||||
@Resource
|
||||
private IPgsProgressPlanDetailService progressPlanDetailService;
|
||||
|
||||
@Resource
|
||||
private IPgsProgressPlanService progressPlanService;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
RecognizeVo recognize = recognizerManager.recognize("http://xny.yj-3d.com:7363/file/tif/20250625160218orthophoto.png", List.of(RecognizerTypeEnum.PHO));
|
||||
RecognizeVo recognize = recognizerManager.recognize("http://xny.yj-3d.com:7363/file/tif/20250625160218orthophoto.png", List.of(RecognizerTypeEnum.PANEL));
|
||||
log.info("recognize: {}", recognize);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChange() {
|
||||
recognizerManager.convertCoordinate("http://xny.yj-3d.com:9000/xinnengyuan-dev/2025/11/11/d48767a62bc04867a552e06ba6712004.tif", List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkNumber() {
|
||||
List<PgsProgressPlanDetail> detailList = progressPlanDetailService.list();
|
||||
List<PgsProgressPlanDetail> planDetailList = detailList.stream()
|
||||
.filter(detail -> StringUtils.isNotBlank(detail.getFinishedDetail()))
|
||||
.toList();
|
||||
Map<String, List<PgsProgressPlanDetail>> collect = planDetailList.stream()
|
||||
.collect(Collectors.groupingBy(PgsProgressPlanDetail::getFinishedDetail));
|
||||
// 遍历分组结果,输出重复项
|
||||
for (Map.Entry<String, List<PgsProgressPlanDetail>> entry : collect.entrySet()) {
|
||||
String fieldValue = entry.getKey();
|
||||
List<PgsProgressPlanDetail> duplicates = entry.getValue();
|
||||
if (duplicates.size() > 1) { // 筛选出有重复的字段值
|
||||
PgsProgressPlanDetail first = duplicates.getFirst();
|
||||
Long id = first.getId();
|
||||
Long planId = first.getProgressPlanId();
|
||||
progressPlanService.removeById(planId);
|
||||
progressPlanDetailService.removeById(id);
|
||||
System.out.println("重复对象的对象:" + duplicates.stream().map(PgsProgressPlanDetail::getId).toList());
|
||||
}
|
||||
}
|
||||
for (PgsProgressPlanDetail detail : detailList) {
|
||||
BigDecimal aiFill = detail.getFinishedNumber();
|
||||
String finishedDetail = detail.getFinishedDetail();
|
||||
if (StringUtils.isBlank(finishedDetail)) {
|
||||
continue;
|
||||
}
|
||||
// log.info("id: {}, finishedDetail: {}", detail.getId(), finishedDetail);
|
||||
List<PgsProgressPlanDetailFinishedVo> finishedVos = JSONUtil.toList(finishedDetail, PgsProgressPlanDetailFinishedVo.class);
|
||||
if (aiFill.compareTo(BigDecimal.valueOf(finishedVos.size())) != 0) {
|
||||
log.info("数量异常:{}:{}", detail.getId(), detail.getProgressCategoryId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
package org.dromara.test;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.safety.domain.dto.violationrecord.HseViolationRecordCreateDto;
|
||||
import org.dromara.safety.service.IHseViolationRecordService;
|
||||
import org.dromara.system.domain.SysMenu;
|
||||
import org.dromara.system.domain.vo.SysMenuVo;
|
||||
import org.dromara.system.mapper.SysMenuMapper;
|
||||
import org.dromara.system.service.ISysMenuService;
|
||||
import org.hamcrest.core.Is;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -20,6 +28,9 @@ public class ViolationRecordTest {
|
||||
@Resource
|
||||
private IHseViolationRecordService violationRecordService;
|
||||
|
||||
@Autowired
|
||||
private SysMenuMapper sysMenuMapper;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
List<HseViolationRecordCreateDto> list = new ArrayList<>();
|
||||
@ -55,4 +66,26 @@ public class ViolationRecordTest {
|
||||
list.add(dto5);
|
||||
violationRecordService.insertByMonitor(list);
|
||||
}
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
SysMenuVo sysMenuVo = sysMenusByList(1972500768346673154L);
|
||||
System.out.println(sysMenuVo.toString());
|
||||
|
||||
}
|
||||
|
||||
SysMenuVo sysMenusByList(Long id) {
|
||||
SysMenuVo sysMenus = new SysMenuVo();
|
||||
List<SysMenu> res = sysMenuMapper.selectList(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getParentId, id));
|
||||
if (res != null && res.size() > 0 ) {
|
||||
sysMenus.setChildren(MapstructUtils.convert(res, SysMenuVo.class));
|
||||
res.forEach(sysMenu -> {
|
||||
sysMenusByList(sysMenu.getMenuId());
|
||||
});
|
||||
}
|
||||
return sysMenus;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -5,11 +5,14 @@ import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.task.VirtualThreadTaskExecutor;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* 异步配置
|
||||
@ -26,12 +29,48 @@ public class AsyncConfig implements AsyncConfigurer {
|
||||
*/
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
if(SpringUtils.isVirtual()) {
|
||||
if (SpringUtils.isVirtual()) {
|
||||
return new VirtualThreadTaskExecutor("async-");
|
||||
}
|
||||
return SpringUtils.getBean("scheduledExecutorService");
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增:自定义线程池(可以在 @Async("capturePicExecutor") 使用)
|
||||
*/
|
||||
@Bean("capturePicExecutor")
|
||||
public Executor customExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
// 核心线程数
|
||||
executor.setCorePoolSize(5);
|
||||
// 最大线程数
|
||||
executor.setMaxPoolSize(10);
|
||||
// 队列容量(超过核心线程数时,任务进入队列)
|
||||
executor.setQueueCapacity(50);
|
||||
// 空闲线程最大存活时间(秒)
|
||||
executor.setKeepAliveSeconds(60);
|
||||
// 线程名前缀,方便定位日志
|
||||
executor.setThreadNamePrefix("capturePic-async-");
|
||||
// 拒绝策略:当线程池满时
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
// CallerRunsPolicy:由调用线程执行任务(相对安全)
|
||||
// 初始化
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
// 解压线程池(大文件操作,不需要太多线程)
|
||||
@Bean("unzipExecutor")
|
||||
public Executor unzipExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(2);
|
||||
executor.setMaxPoolSize(4);
|
||||
executor.setQueueCapacity(10);
|
||||
executor.setThreadNamePrefix("unzip-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步执行异常处理
|
||||
*/
|
||||
|
||||
@ -80,4 +80,9 @@ public interface CacheNames {
|
||||
*/
|
||||
String ONLINE_TOKEN = "online_tokens";
|
||||
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
String PROJECT_NAME = "project_name#30d";
|
||||
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ public interface Constants {
|
||||
/**
|
||||
* 验证码有效期(分钟)
|
||||
*/
|
||||
Integer CAPTCHA_EXPIRATION = 2;
|
||||
Integer CAPTCHA_EXPIRATION = 5;
|
||||
|
||||
/**
|
||||
* 顶级父级id
|
||||
|
||||
@ -70,4 +70,9 @@ public class UserDTO implements Serializable {
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatarUrl;
|
||||
|
||||
}
|
||||
|
||||
@ -30,4 +30,13 @@ public class RegisterBody extends LoginBody {
|
||||
|
||||
private String userType;
|
||||
|
||||
private Long projectId;
|
||||
|
||||
private String email;
|
||||
|
||||
private String phonenumber;
|
||||
|
||||
private Long deptId;
|
||||
|
||||
private String smsCode;
|
||||
}
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
package org.dromara.common.core.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
@Data
|
||||
public class XzdCustomerSupplierVos implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 类型(1、供应商,2、客户)
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 供应商-客户id
|
||||
*/
|
||||
private Long cSId;
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-09-10 16:15
|
||||
*/
|
||||
public interface ProjectService {
|
||||
|
||||
/**
|
||||
* 通过项目ID查询项目名称
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 项目名称
|
||||
*/
|
||||
String selectProjectNameById(Long projectId);
|
||||
|
||||
}
|
||||
@ -124,4 +124,20 @@ public interface UserService {
|
||||
*/
|
||||
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
|
||||
|
||||
|
||||
/**
|
||||
* 通过用户id查询头像
|
||||
*/
|
||||
String selectAvatarById(Long userId);
|
||||
|
||||
/**
|
||||
* 通过用户id查询用户信息
|
||||
*/
|
||||
UserDTO selectUser(Long userId);
|
||||
|
||||
/**
|
||||
* 查询对应专业的用户Id
|
||||
*/
|
||||
List<String> selectUserByProfession(String code,String businessId,String type,Long projectId);
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
public interface XzdCbysZjhcbService {
|
||||
String selectNmaeByIds(String ids);
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
/**
|
||||
* 机械合同信息Service接口
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2025-11-12
|
||||
*/
|
||||
public interface XzdContractMachineryService {
|
||||
|
||||
String selectNmaeByIds(String ids);
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
public interface XzdCsContractInformationService {
|
||||
|
||||
String selectNameByIds(String ids);
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
import org.dromara.common.core.domain.vo.XzdCustomerSupplierVos;
|
||||
|
||||
public interface XzdCustomerSupplierService {
|
||||
|
||||
/**
|
||||
* 查询供应商-客户中间
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 供应商-客户中间
|
||||
*/
|
||||
XzdCustomerSupplierVos queryByIdone(Long id);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
public interface XzdCustomerinformationService {
|
||||
|
||||
String selectNmaeByIds(String id);
|
||||
|
||||
String selectNmaeById(Long id);
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
public interface XzdJsCgJungonService {
|
||||
|
||||
String selectNameByIds(String ids);
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
public interface XzdProjectManagerApprovalService {
|
||||
|
||||
String selectNmaeByIds(String ids);
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
public interface XzdProjectService {
|
||||
|
||||
String selectNmaeByIds(String ids);
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
public interface XzdPurchaseContractInformationService {
|
||||
String selectNameByIds(String ids);
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
public interface XzdSjContractInfoService {
|
||||
|
||||
String selectNameByIds(String ids);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
public interface XzdSupplierInfoService {
|
||||
|
||||
String selectNmaeByIds(String ids);
|
||||
|
||||
String selectNmaeById(Long id);
|
||||
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
public interface XzdSupplierOpenBankService {
|
||||
|
||||
String selectNameByIds(String ids);
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
package org.dromara.common.core.utils;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* 时间戳转换工具类(包含时分秒提取)
|
||||
*/
|
||||
public class TimestampUtils {
|
||||
|
||||
// 默认时区(东八区)
|
||||
private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("GMT+8");
|
||||
|
||||
// 完整日期格式
|
||||
private static final String FULL_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
// 时分秒格式
|
||||
private static final String TIME_FORMAT = "HH:mm:ss";
|
||||
|
||||
// 日期格式
|
||||
private static final String DATE_FORMAT = "yyyy-MM-dd";
|
||||
|
||||
/**
|
||||
* 将日期字符串转为秒级时间戳(优化版)
|
||||
* 支持格式:"2025-12-03 23:59:59"、"2025-12-03"等
|
||||
* @param dateStr 日期字符串
|
||||
* @return 秒级时间戳
|
||||
*/
|
||||
public static Long parseDateToTimestamp(String dateStr) {
|
||||
if (StrUtil.isBlank(dateStr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 使用Hutool的DateUtil进行智能解析(推荐)
|
||||
try {
|
||||
Date date = DateUtil.parse(dateStr);
|
||||
return date.getTime() / 1000L; // 转为秒级时间戳
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("日期格式错误:%s,支持格式:yyyy-MM-dd HH:mm:ss、yyyy-MM-dd等", dateStr)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将时间戳转换为完整日期格式(yyyy-MM-dd HH:mm:ss)
|
||||
*/
|
||||
public static String formatTimestamp(Object timestamp) {
|
||||
if (timestamp == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String timestampStr = timestamp.toString().trim();
|
||||
if (StrUtil.isBlank(timestampStr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Long time = parseToMilliseconds(timestampStr);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(FULL_DATE_FORMAT);
|
||||
sdf.setTimeZone(DEFAULT_TIME_ZONE);
|
||||
return sdf.format(new Date(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取时间戳中的时分秒部分(HH:mm:ss)
|
||||
*/
|
||||
public static String extractTime(Object timestamp) {
|
||||
if (timestamp == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String timestampStr = timestamp.toString().trim();
|
||||
if (StrUtil.isBlank(timestampStr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Long time = parseToMilliseconds(timestampStr);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(TIME_FORMAT);
|
||||
sdf.setTimeZone(DEFAULT_TIME_ZONE);
|
||||
return sdf.format(new Date(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取时间戳中的日期部分(yyyy-MM-dd)
|
||||
*/
|
||||
public static String extractDate(Object timestamp) {
|
||||
if (timestamp == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String timestampStr = timestamp.toString().trim();
|
||||
if (StrUtil.isBlank(timestampStr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Long time = parseToMilliseconds(timestampStr);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
|
||||
sdf.setTimeZone(DEFAULT_TIME_ZONE);
|
||||
return sdf.format(new Date(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析时间戳字符串为毫秒级时间戳
|
||||
*/
|
||||
private static Long parseToMilliseconds(String timestampStr) {
|
||||
try {
|
||||
Long time = Long.parseLong(timestampStr);
|
||||
// 10位秒级时间戳转为13位毫秒级
|
||||
if (timestampStr.length() == 10) {
|
||||
time = time * 1000;
|
||||
}
|
||||
return time;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("时间戳格式错误:" + timestampStr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时间戳对应的小时
|
||||
*/
|
||||
public static int getHour(Object timestamp) {
|
||||
String timeStr = extractTime(timestamp);
|
||||
return Integer.parseInt(timeStr.split(":")[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时间戳对应的分钟
|
||||
*/
|
||||
public static int getMinute(Object timestamp) {
|
||||
String timeStr = extractTime(timestamp);
|
||||
return Integer.parseInt(timeStr.split(":")[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时间戳对应的秒
|
||||
*/
|
||||
public static int getSecond(Object timestamp) {
|
||||
String timeStr = extractTime(timestamp);
|
||||
return Integer.parseInt(timeStr.split(":")[2]);
|
||||
}
|
||||
}
|
||||
@ -44,7 +44,17 @@
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
<version>4.4.0</version>
|
||||
<version>4.5.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package org.dromara.common.doc.config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.Paths;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
@ -9,6 +11,7 @@ import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.doc.config.properties.SpringDocProperties;
|
||||
import org.dromara.common.doc.handler.OpenApiHandler;
|
||||
import org.springdoc.core.configuration.SpringDocConfiguration;
|
||||
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
|
||||
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
||||
import org.springdoc.core.customizers.OpenApiCustomizer;
|
||||
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
|
||||
@ -23,6 +26,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -61,7 +65,6 @@ public class SpringDocConfig {
|
||||
keySet.forEach(securityRequirement::addList);
|
||||
list.add(securityRequirement);
|
||||
openApi.security(list);
|
||||
|
||||
return openApi;
|
||||
}
|
||||
|
||||
@ -123,4 +126,26 @@ public class SpringDocConfig {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public GlobalOpenApiCustomizer orderGlobalOpenApiCustomizer() {
|
||||
return openApi -> {
|
||||
// 全局添加鉴权参数
|
||||
if(openApi.getPaths()!=null){
|
||||
openApi.getPaths().forEach((s, pathItem) -> {
|
||||
// 为所有接口添加鉴权
|
||||
List<Operation> operations = pathItem.readOperations();
|
||||
operations.forEach(operation -> {
|
||||
operation.addSecurityItem(new SecurityRequirement()
|
||||
.addList(HttpHeaders.AUTHORIZATION)
|
||||
.addList("Clientid")
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
package org.dromara.common.excel.coryUtils;
|
||||
|
||||
import org.apache.poi.hssf.usermodel.HSSFSheet;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@ -76,15 +73,14 @@ public class ExcelReader {
|
||||
|
||||
// 从MultipartFile获取输入流
|
||||
try (InputStream stream = file.getInputStream();
|
||||
org.apache.poi.ss.usermodel.Workbook workbook = isXlsx ?
|
||||
new XSSFWorkbook(stream) : new HSSFWorkbook(stream)) {
|
||||
Workbook workbook = WorkbookFactory.create(stream)) {
|
||||
|
||||
for (int sheetIndex = 0; sheetIndex < workbook.getNumberOfSheets(); sheetIndex++) {
|
||||
org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(sheetIndex);
|
||||
SheetData sheetData = new SheetData();
|
||||
sheetData.setSheetName(sheet.getSheetName());
|
||||
|
||||
processSheetData(sheet, sheetData);
|
||||
processSheetData(workbook,sheet, sheetData);
|
||||
sheetDataList.add(sheetData);
|
||||
}
|
||||
}
|
||||
@ -114,7 +110,7 @@ public class ExcelReader {
|
||||
sheetData.setSheetName(sheet.getSheetName());
|
||||
|
||||
// 处理单个sheet的数据
|
||||
processSheetData(sheet, sheetData);
|
||||
processSheetData(workbook, sheet, sheetData);
|
||||
|
||||
sheetDataList.add(sheetData);
|
||||
}
|
||||
@ -129,7 +125,7 @@ public class ExcelReader {
|
||||
/**
|
||||
* 处理单个工作表的数据
|
||||
*/
|
||||
private static void processSheetData(org.apache.poi.ss.usermodel.Sheet sheet, SheetData sheetData) {
|
||||
private static void processSheetData(Workbook workbook, Sheet sheet, SheetData sheetData) {
|
||||
boolean foundChineseStart = false;
|
||||
List<List<String>> data = new ArrayList<>();
|
||||
boolean isFirstRow = true;
|
||||
@ -140,12 +136,12 @@ public class ExcelReader {
|
||||
isFirstRow = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(hasValidData(workbook,row)){
|
||||
List<String> rowData = new ArrayList<>();
|
||||
// 读取A到E列(索引0到4)
|
||||
for (int cellIndex = 0; cellIndex < 6; cellIndex++) {
|
||||
Cell cell = row.getCell(cellIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
|
||||
rowData.add(getCellValue(cell));
|
||||
rowData.add(getCellValue(workbook,cell));
|
||||
}
|
||||
|
||||
// 检查是否找到中文数字开头的行
|
||||
@ -160,9 +156,24 @@ public class ExcelReader {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sheetData.setData(data);
|
||||
}
|
||||
|
||||
private static boolean hasValidData(Workbook workbook,Row row) {
|
||||
// 遍历行中的所有单元格
|
||||
for (int cellIndex = 0; cellIndex < row.getLastCellNum(); cellIndex++) {
|
||||
Cell cell = row.getCell(cellIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
|
||||
String cellValue = getCellValue(workbook, cell).trim();
|
||||
|
||||
// 只要有一个单元格有非空值,就认为是有效行
|
||||
if (!cellValue.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 根据数据构建树形结构
|
||||
*/
|
||||
@ -263,7 +274,7 @@ public class ExcelReader {
|
||||
/**
|
||||
* 获取单元格的值,处理不同数据类型
|
||||
*/
|
||||
private static String getCellValue(Cell cell) {
|
||||
private static String getCellValue(Workbook workbook, Cell cell) {
|
||||
if (cell == null) {
|
||||
return "";
|
||||
}
|
||||
@ -278,6 +289,21 @@ public class ExcelReader {
|
||||
return numericValue.substring(0, numericValue.length() - 2);
|
||||
}
|
||||
return numericValue;
|
||||
case FORMULA:
|
||||
//这样对于字符串cell.getStringCellValue()方法即可取得其值,如果公式生成的是数值,使用cell.getStringCellValue()方法会抛出IllegalStateException异常,在异常处理中使用cell.getNumericCellValue();
|
||||
// 1. 获取公式文本(如 "A1+B1")
|
||||
String formula = cell.getCellFormula();
|
||||
System.out.println("公式文本:" + formula);
|
||||
|
||||
// 2. 创建公式计算器(关键步骤:用于计算公式结果)
|
||||
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
|
||||
|
||||
// 3. 计算公式,返回包含结果类型和值的CellValue对象
|
||||
CellValue cellValue = evaluator.evaluate(cell);
|
||||
|
||||
// 4. 根据结果类型提取值
|
||||
return getValue( cellValue.getCellType(), cellValue);
|
||||
|
||||
case BOOLEAN:
|
||||
return String.valueOf(cell.getBooleanCellValue());
|
||||
default:
|
||||
@ -285,6 +311,19 @@ public class ExcelReader {
|
||||
}
|
||||
}
|
||||
|
||||
private static String getValue( CellType resultType, CellValue cellValue) {
|
||||
return switch (resultType) {
|
||||
case NUMERIC -> String.valueOf(cellValue.getNumberValue());
|
||||
case STRING -> String.valueOf(cellValue.getStringValue());
|
||||
case BOOLEAN -> String.valueOf(cellValue.getBooleanValue());
|
||||
case ERROR -> String.valueOf(cellValue.getErrorValue());
|
||||
case BLANK -> String.valueOf(CellType.BLANK);
|
||||
default ->
|
||||
// 保留原公式(不处理的类型)
|
||||
cellValue.getStringValue();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据A列的值确定父节点的键
|
||||
*/
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
package org.dromara.common.excel.handler;
|
||||
|
||||
import com.alibaba.excel.write.handler.CellWriteHandler;
|
||||
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class LockColumnHandler implements CellWriteHandler {
|
||||
|
||||
private final Set<Integer> lockColumns;
|
||||
|
||||
public LockColumnHandler(Collection<Integer> lockColumns) {
|
||||
this.lockColumns = new HashSet<>(lockColumns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCellDispose(CellWriteHandlerContext context) {
|
||||
Cell cell = context.getCell();
|
||||
if (cell == null) return;
|
||||
|
||||
Workbook workbook = cell.getSheet().getWorkbook();
|
||||
|
||||
// 必须为所有单元格创建一个新的 CellStyle
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
|
||||
// 锁定指定列
|
||||
if (lockColumns.contains(cell.getColumnIndex())) {
|
||||
style.setLocked(true);
|
||||
} else {
|
||||
style.setLocked(false); // 非锁定列必须明确设置为 false!
|
||||
}
|
||||
|
||||
cell.setCellStyle(style);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package org.dromara.common.excel.handler;
|
||||
|
||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
||||
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-11-14 15:05
|
||||
*/
|
||||
public class SheetProtectHandler implements SheetWriteHandler {
|
||||
|
||||
private final String password;
|
||||
|
||||
public SheetProtectHandler(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder,
|
||||
WriteSheetHolder writeSheetHolder) {
|
||||
Sheet sheet = writeSheetHolder.getSheet();
|
||||
sheet.protectSheet(password); // 可为空字符串
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,10 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.resource.ClassPathResource;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.ExcelReader;
|
||||
import com.alibaba.excel.ExcelWriter;
|
||||
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
|
||||
import com.alibaba.excel.read.metadata.ReadSheet;
|
||||
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
|
||||
import com.alibaba.excel.write.metadata.WriteSheet;
|
||||
import com.alibaba.excel.write.metadata.fill.FillConfig;
|
||||
@ -14,27 +17,27 @@ import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.file.FileUtils;
|
||||
import org.dromara.common.excel.convert.ExcelBigNumberConvert;
|
||||
import org.dromara.common.excel.core.*;
|
||||
import org.dromara.common.excel.handler.DataWriteHandler;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.dromara.common.excel.handler.LockColumnHandler;
|
||||
import org.dromara.common.excel.handler.SheetProtectHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Excel相关处理
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class ExcelUtil {
|
||||
|
||||
@ -48,6 +51,41 @@ public class ExcelUtil {
|
||||
return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步读取 Excel(支持读取多个 Sheet)
|
||||
*
|
||||
* @param is 输入流(一次性读取)
|
||||
* @param clazz Excel 映射实体类
|
||||
* @return 所有 Sheet 的数据汇总
|
||||
*/
|
||||
public static <T> List<T> importExcelAllSheet(InputStream is, Class<T> clazz) {
|
||||
// 用于存放所有 sheet 的数据
|
||||
List<T> allData = new ArrayList<>();
|
||||
// 1. 构建 ExcelReader
|
||||
ExcelReaderBuilder readerBuilder = EasyExcel.read(is);
|
||||
ExcelReader reader = readerBuilder.build();
|
||||
// 2. 获取 Excel 中全部 Sheet 信息(包含 SheetNo、页名等)
|
||||
List<ReadSheet> readSheets = reader.excelExecutor().sheetList();
|
||||
// 3. 遍历每一个 Sheet
|
||||
for (ReadSheet sheet : readSheets) {
|
||||
// 为每个 Sheet 创建独立监听器,用于接收读取结果
|
||||
DefaultExcelListener<T> listener = new DefaultExcelListener<>(false);
|
||||
// 4. 构建当前 Sheet 的读取器 使用 sheet.getSheetNo() 指定当前 sheet
|
||||
ReadSheet readSheet = EasyExcel.readSheet(sheet.getSheetNo())
|
||||
// 设置头映射实体类
|
||||
.head(clazz)
|
||||
// 注册读取监听器
|
||||
.registerReadListener(listener)
|
||||
.build();
|
||||
// 5. 开始读取当前 Sheet
|
||||
reader.read(readSheet);
|
||||
// 6. 收集当前 Sheet 读取的数据
|
||||
allData.addAll(listener.getExcelResult().getList());
|
||||
}
|
||||
// 7. 关闭读取器,释放资源
|
||||
reader.finish();
|
||||
return allData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用校验监听器 异步导入 同步返回
|
||||
@ -205,6 +243,37 @@ public class ExcelUtil {
|
||||
builder.doWrite(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出excel
|
||||
*
|
||||
* @param list 导出数据集合
|
||||
* @param sheetName 工作表的名称
|
||||
* @param clazz 实体类
|
||||
* @param lockColumns 锁定列
|
||||
* @param merge 是否合并单元格
|
||||
* @param os 输出流
|
||||
*/
|
||||
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, List<Integer> lockColumns,
|
||||
boolean merge, OutputStream os, List<DropDownOptions> options) {
|
||||
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
|
||||
.registerWriteHandler(new LockColumnHandler(lockColumns)) // 锁定第3列
|
||||
.registerWriteHandler(new SheetProtectHandler("dawdawdwad")) // 保护整张 sheet
|
||||
.autoCloseStream(false)
|
||||
// 自动适配
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||
// 大数值自动转换 防止失真
|
||||
.registerConverter(new ExcelBigNumberConvert())
|
||||
.registerWriteHandler(new DataWriteHandler(clazz))
|
||||
.sheet(sheetName);
|
||||
if (merge) {
|
||||
// 合并处理器
|
||||
builder.registerWriteHandler(new CellMergeStrategy(list, true));
|
||||
}
|
||||
// 添加下拉框操作
|
||||
builder.registerWriteHandler(new ExcelDownHandler(options));
|
||||
builder.doWrite(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出多sheet excel(增强版,解决XML安全问题)
|
||||
*
|
||||
@ -213,7 +282,9 @@ public class ExcelUtil {
|
||||
* @param clazz 实体类
|
||||
* @param optionsList 级联下拉选内容列表
|
||||
*/
|
||||
public static <T> void exportMultiSheetExcelEnhanced(List<List<T>> sheetData, List<String> sheetNames, Class<T> clazz, List<List<DropDownOptions>> optionsList,HttpServletResponse response) throws IOException {
|
||||
public static <T> void exportMultiSheetExcelEnhanced(List<List<T>> sheetData, List<String> sheetNames,
|
||||
Class<T> clazz, List<List<DropDownOptions>> optionsList,
|
||||
HttpServletResponse response) throws IOException {
|
||||
resetResponse("file", response);
|
||||
ExcelWriter excelWriter = null;
|
||||
ServletOutputStream os = response.getOutputStream();
|
||||
@ -224,6 +295,9 @@ public class ExcelUtil {
|
||||
.head(clazz)
|
||||
.autoCloseStream(false)
|
||||
.registerConverter(new ExcelBigNumberConvert())
|
||||
// 自动适配
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||
.registerWriteHandler(new DataWriteHandler(clazz))
|
||||
.build();
|
||||
|
||||
|
||||
@ -252,12 +326,71 @@ public class ExcelUtil {
|
||||
excelWriter.finish();
|
||||
} catch (Exception e) {
|
||||
// 记录日志但不中断主流程
|
||||
e.printStackTrace();
|
||||
log.error("Excel 导出错误", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出多sheet excel(增强版,解决XML安全问题)
|
||||
*
|
||||
* @param sheetData 多个sheet的数据
|
||||
* @param sheetNames 多个sheet的名称
|
||||
* @param clazz 实体类
|
||||
* @param optionsList 级联下拉选内容列表
|
||||
*/
|
||||
public static <T> void exportExcel(List<List<T>> sheetData, List<String> sheetNames, List<Integer> lockColumns,
|
||||
Class<T> clazz, List<List<DropDownOptions>> optionsList,
|
||||
HttpServletResponse response) throws IOException {
|
||||
resetResponse("file", response);
|
||||
ExcelWriter excelWriter = null;
|
||||
ServletOutputStream os = response.getOutputStream();
|
||||
|
||||
try {
|
||||
// 使用SXSSFWorkbook避免内存问题,并减少XML处理复杂度
|
||||
excelWriter = EasyExcel.write(os)
|
||||
.head(clazz)
|
||||
.registerWriteHandler(new LockColumnHandler(lockColumns)) // 锁定第3列
|
||||
// .registerWriteHandler(new SheetProtectHandler("dawdawdwad")) // 保护整张 sheet
|
||||
.autoCloseStream(false)
|
||||
.registerConverter(new ExcelBigNumberConvert())
|
||||
// 自动适配
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||
.registerWriteHandler(new DataWriteHandler(clazz))
|
||||
.build();
|
||||
|
||||
|
||||
// 为每个sheet写入数据
|
||||
for (int i = 0; i < sheetData.size(); i++) {
|
||||
// 创建基本sheet配置
|
||||
WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetNames.get(i))
|
||||
.head(clazz)
|
||||
.build();
|
||||
|
||||
// 添加下拉选项(如果存在)
|
||||
if (optionsList != null && optionsList.size() > i && optionsList.get(i) != null) {
|
||||
ExcelDownHandler handler = new ExcelDownHandler(optionsList.get(i));
|
||||
writeSheet.setCustomWriteHandlerList(
|
||||
Collections.singletonList(handler));
|
||||
}
|
||||
|
||||
// 写入数据
|
||||
excelWriter.write(sheetData.get(i), writeSheet);
|
||||
}
|
||||
|
||||
} finally {
|
||||
// 确保资源正确释放
|
||||
if (excelWriter != null) {
|
||||
try {
|
||||
excelWriter.finish();
|
||||
} catch (Exception e) {
|
||||
// 记录日志但不中断主流程
|
||||
log.error("Excel 导出错误", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单表多数据模板导出 模板格式为 {.属性}
|
||||
@ -493,6 +626,4 @@ public class ExcelUtil {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -91,7 +91,7 @@ public class PlusDataPermissionHandler {
|
||||
DataPermissionHelper.setVariable("user", currentUser);
|
||||
}
|
||||
// 如果是超级管理员或租户管理员,则不过滤数据
|
||||
if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
|
||||
if (LoginHelper.isSuperAdmin()) {
|
||||
return where;
|
||||
}
|
||||
// 构造数据过滤条件的 SQL 片段
|
||||
|
||||
@ -183,7 +183,7 @@ public class OssClient {
|
||||
// 创建异步请求体(length如果为空会报错)
|
||||
BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
|
||||
.contentLength(length)
|
||||
.subscribeTimeout(Duration.ofSeconds(30))
|
||||
.subscribeTimeout(Duration.ofSeconds(120)) // 增加超时时间到120秒
|
||||
.build();
|
||||
|
||||
// 使用 transferManager 进行上传
|
||||
|
||||
@ -77,7 +77,7 @@ public class RedisUtils {
|
||||
public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
|
||||
RTopic topic = CLIENT.getTopic(channelKey);
|
||||
topic.publish(msg);
|
||||
System.out.println("发布通道消息---------"+msg.toString());
|
||||
// System.out.println("发布通道消息---------"+msg.toString());
|
||||
consumer.accept(msg);
|
||||
}
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ public class SaPermissionImpl implements StpInterface {
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
UserType userType = UserType.getUserType(loginUser.getUserType());
|
||||
if (userType == UserType.SYS_USER) {
|
||||
if (userType == UserType.SYS_USER || userType == UserType.APP_USER) {
|
||||
Long projectId = loginUser.getProjectId();
|
||||
List<SysProjectRoleMenuVo> menuPermission = loginUser.getMenuPermission();
|
||||
if (CollUtil.isNotEmpty(menuPermission)) {
|
||||
@ -51,9 +51,10 @@ public class SaPermissionImpl implements StpInterface {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
} else if (userType == UserType.APP_USER) {
|
||||
// 其他端 自行根据业务编写
|
||||
}
|
||||
/* else if (userType == UserType.APP_USER) {
|
||||
// 其他端 自行根据业务编写
|
||||
}*/
|
||||
return new ArrayList<>();
|
||||
// return Collections.singletonList("*");
|
||||
}
|
||||
@ -65,7 +66,7 @@ public class SaPermissionImpl implements StpInterface {
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
UserType userType = UserType.getUserType(loginUser.getUserType());
|
||||
if (userType == UserType.SYS_USER) {
|
||||
if (userType == UserType.SYS_USER || userType == UserType.APP_USER) {
|
||||
Long projectId = loginUser.getProjectId();
|
||||
List<SysProjectRolePermissionVo> rolePermission = loginUser.getRolePermission();
|
||||
if (CollUtil.isNotEmpty(rolePermission)) {
|
||||
@ -87,9 +88,10 @@ public class SaPermissionImpl implements StpInterface {
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
} else if (userType == UserType.APP_USER) {
|
||||
// 其他端 自行根据业务编写
|
||||
}
|
||||
/* else if (userType == UserType.APP_USER) {
|
||||
// 其他端 自行根据业务编写
|
||||
}*/
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import cn.hutool.core.map.MapUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.common.sse.dto.SseMessageDto;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -22,7 +23,14 @@ public class SseEmitterManager {
|
||||
/**
|
||||
* 订阅的频道
|
||||
*/
|
||||
private final static String SSE_TOPIC = "global:sse";
|
||||
// private final static String SSE_TOPIC = "global:sse";
|
||||
|
||||
private static String SSE_TOPIC;
|
||||
|
||||
@Value("${spring.data.redis.database:0}")
|
||||
public void setDatabase(int database) {
|
||||
SSE_TOPIC = "global:sse:db" + database;
|
||||
}
|
||||
|
||||
private final static Map<Long, Map<String, SseEmitter>> USER_TOKEN_EMITTERS = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
@ -32,4 +32,60 @@ public interface TransConstant {
|
||||
*/
|
||||
String OSS_ID_TO_URL = "oss_id_to_url";
|
||||
|
||||
/**
|
||||
* 项目id转名称
|
||||
*/
|
||||
String PROJECT_ID_TO_NAME = "project_id_to_name";
|
||||
|
||||
|
||||
/**
|
||||
* 客户id转名称
|
||||
*/
|
||||
String XZD_KHXX_ID_TO_NAME = "khxx_id_to_name";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 新中大项目id转名称
|
||||
*/
|
||||
String XZD_PROJECT_ID_TO_NAME = "xzd_project_id_to_name";
|
||||
/**
|
||||
* 采购合同id转名称
|
||||
*/
|
||||
String XZD_PURCHASE_CONTRACT_ID_TO_NAME = "xzd_purchase_contract_id_to_name";
|
||||
|
||||
/**
|
||||
* 机械合同id转名称
|
||||
*/
|
||||
String XZD_PURCHASE_JXXIE_ID_TO_NAME = "xzd_purchase_contract_jixie_id_to_name";
|
||||
|
||||
/**
|
||||
* 综合服务合同id转名称
|
||||
*/
|
||||
String XZD_CS_CONTRACT_INFORMATION_ID_TO_NAME = "xzd_cs_contract_information_id_to_name";
|
||||
/**
|
||||
* 结算-采购合同竣工结算id转名称
|
||||
*/
|
||||
String XZD_JS_CG_JUNGON_ID_TO_NAME = "xzd_js_cg_jungon_id_to_name";
|
||||
/**
|
||||
* 新中大供应商信息-开户银行id转银行名称
|
||||
*/
|
||||
String XZD_SUPPLIER_OPEN_BANK_ID_TO_NAME = "xzd_supplier_open_bank_id_to_name";
|
||||
|
||||
|
||||
/**
|
||||
* 新中大-项目经理推荐及审批id转名称
|
||||
*/
|
||||
String XZD_PROJECT_MANAGER_APPROVAL_ID_TO_NAME = "xzd_project_manager_approval_id_to_name";
|
||||
/**
|
||||
* 新中大-立项及成本-成本预算-总体计划成本id转名称
|
||||
*/
|
||||
String XZD_CBYS_ZJHCB_ID_TO_NAME = "xzd_cbys_zjhcb_id_to_name";
|
||||
|
||||
/**
|
||||
* 新中大-设计合同信息id转name
|
||||
*/
|
||||
String XZD_SJ_CONTRACT_INFO_ID_TO_NAME = "xzd_sj_contract_info_id_to_name";
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
package org.dromara.common.translation.core.impl;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.dromara.common.core.service.ProjectService;
|
||||
import org.dromara.common.translation.annotation.TranslationType;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-09-10 16:13
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@TranslationType(type = TransConstant.PROJECT_ID_TO_NAME)
|
||||
public class ProjectNameTranslationImpl implements TranslationInterface<String> {
|
||||
|
||||
@Resource
|
||||
private ProjectService projectService;
|
||||
|
||||
/**
|
||||
* 翻译
|
||||
*
|
||||
* @param key 需要被翻译的键(不为空)
|
||||
* @param other 其他参数
|
||||
* @return 返回键对应的值
|
||||
*/
|
||||
@Override
|
||||
public String translation(Object key, String other) {
|
||||
if (key instanceof Long id) {
|
||||
return projectService.selectProjectNameById(id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package org.dromara.common.translation.core.impl;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.dromara.common.core.service.XzdCbysZjhcbService;
|
||||
import org.dromara.common.core.service.XzdProjectManagerApprovalService;
|
||||
import org.dromara.common.translation.annotation.TranslationType;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
|
||||
@AllArgsConstructor
|
||||
@TranslationType(type = TransConstant.XZD_CBYS_ZJHCB_ID_TO_NAME)
|
||||
public class XzdCbysZjhcbImpl implements TranslationInterface<String> {
|
||||
private final XzdCbysZjhcbService xzdCbysZjhcbService;
|
||||
|
||||
@Override
|
||||
public String translation(Object key, String other) {
|
||||
if (key instanceof String ids) {
|
||||
return xzdCbysZjhcbService.selectNmaeByIds(ids);
|
||||
} else if (key instanceof Long id) {
|
||||
return xzdCbysZjhcbService.selectNmaeByIds(id.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package org.dromara.common.translation.core.impl;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.dromara.common.core.service.XzdCsContractInformationService;
|
||||
import org.dromara.common.core.service.XzdPurchaseContractInformationService;
|
||||
import org.dromara.common.translation.annotation.TranslationType;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
|
||||
@AllArgsConstructor
|
||||
@TranslationType(type = TransConstant.XZD_CS_CONTRACT_INFORMATION_ID_TO_NAME)
|
||||
public class XzdCsContractInformationImpl implements TranslationInterface<String> {
|
||||
|
||||
private final XzdCsContractInformationService xzdCsContractInformationService;
|
||||
|
||||
|
||||
@Override
|
||||
public String translation(Object key, String other) {
|
||||
if (key instanceof String ids) {
|
||||
return xzdCsContractInformationService.selectNameByIds(ids);
|
||||
} else if (key instanceof Long id) {
|
||||
return xzdCsContractInformationService.selectNameByIds(id.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package org.dromara.common.translation.core.impl;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.dromara.common.core.service.XzdCustomerinformationService;
|
||||
import org.dromara.common.translation.annotation.TranslationType;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
|
||||
@AllArgsConstructor
|
||||
@TranslationType(type = TransConstant.XZD_KHXX_ID_TO_NAME)
|
||||
public class XzdCustomerinformationImpl implements TranslationInterface<String> {
|
||||
|
||||
private final XzdCustomerinformationService xzdCustomerinformationService;
|
||||
|
||||
@Override
|
||||
public String translation(Object key, String other) {
|
||||
if (key instanceof String ids) {
|
||||
return xzdCustomerinformationService.selectNmaeByIds(ids);
|
||||
} else if (key instanceof Long id) {
|
||||
return xzdCustomerinformationService.selectNmaeByIds(id.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package org.dromara.common.translation.core.impl;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.dromara.common.core.service.XzdContractMachineryService;
|
||||
import org.dromara.common.core.service.XzdJsCgJungonService;
|
||||
import org.dromara.common.translation.annotation.TranslationType;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
|
||||
@AllArgsConstructor
|
||||
@TranslationType(type = TransConstant.XZD_PURCHASE_JXXIE_ID_TO_NAME)
|
||||
public class XzdJXHTTranslation implements TranslationInterface<String> {
|
||||
|
||||
private final XzdContractMachineryService xzdContractMachineryService;
|
||||
|
||||
@Override
|
||||
public String translation(Object key, String other) {
|
||||
if (key instanceof String ids) {
|
||||
return xzdContractMachineryService.selectNmaeByIds(ids);
|
||||
} else if (key instanceof Long id) {
|
||||
return xzdContractMachineryService.selectNmaeByIds(id.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.dromara.common.translation.core.impl;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.dromara.common.core.service.XzdJsCgJungonService;
|
||||
import org.dromara.common.translation.annotation.TranslationType;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
|
||||
@AllArgsConstructor
|
||||
@TranslationType(type = TransConstant.XZD_JS_CG_JUNGON_ID_TO_NAME)
|
||||
public class XzdJsCgJungonImpl implements TranslationInterface<String> {
|
||||
|
||||
private final XzdJsCgJungonService xzdJsCgJungonService;
|
||||
@Override
|
||||
public String translation(Object key, String other) {
|
||||
if (key instanceof String ids) {
|
||||
return xzdJsCgJungonService.selectNameByIds(ids);
|
||||
} else if (key instanceof Long id) {
|
||||
return xzdJsCgJungonService.selectNameByIds(id.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.dromara.common.translation.core.impl;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.dromara.common.core.service.XzdProjectService;
|
||||
import org.dromara.common.translation.annotation.TranslationType;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
|
||||
@AllArgsConstructor
|
||||
@TranslationType(type = TransConstant.XZD_PROJECT_ID_TO_NAME)
|
||||
public class XzdProjectImpl implements TranslationInterface<String> {
|
||||
private final XzdProjectService xzdProjectService;
|
||||
|
||||
@Override
|
||||
public String translation(Object key, String other) {
|
||||
if (key instanceof String ids) {
|
||||
return xzdProjectService.selectNmaeByIds(ids);
|
||||
} else if (key instanceof Long id) {
|
||||
return xzdProjectService.selectNmaeByIds(id.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.dromara.common.translation.core.impl;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.dromara.common.core.service.XzdProjectManagerApprovalService;
|
||||
import org.dromara.common.translation.annotation.TranslationType;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
|
||||
@AllArgsConstructor
|
||||
@TranslationType(type = TransConstant.XZD_PROJECT_MANAGER_APPROVAL_ID_TO_NAME)
|
||||
public class XzdProjectManagerApprovalImpl implements TranslationInterface<String> {
|
||||
private final XzdProjectManagerApprovalService xzdProjectManagerApprovalService;
|
||||
|
||||
@Override
|
||||
public String translation(Object key, String other) {
|
||||
if (key instanceof String ids) {
|
||||
return xzdProjectManagerApprovalService.selectNmaeByIds(ids);
|
||||
} else if (key instanceof Long id) {
|
||||
return xzdProjectManagerApprovalService.selectNmaeByIds(id.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package org.dromara.common.translation.core.impl;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import org.dromara.common.core.service.XzdPurchaseContractInformationService;
|
||||
import org.dromara.common.translation.annotation.TranslationType;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
|
||||
@AllArgsConstructor
|
||||
@TranslationType(type = TransConstant.XZD_PURCHASE_CONTRACT_ID_TO_NAME)
|
||||
public class XzdPurchaseContractInformationImpl implements TranslationInterface<String> {
|
||||
|
||||
private final XzdPurchaseContractInformationService xzdCustomerinformationService;
|
||||
|
||||
|
||||
@Override
|
||||
public String translation(Object key, String other) {
|
||||
if (key instanceof String ids) {
|
||||
return xzdCustomerinformationService.selectNameByIds(ids);
|
||||
} else if (key instanceof Long id) {
|
||||
return xzdCustomerinformationService.selectNameByIds(id.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package org.dromara.common.translation.core.impl;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.dromara.common.core.service.XzdSjContractInfoService;
|
||||
import org.dromara.common.core.service.XzdSupplierOpenBankService;
|
||||
import org.dromara.common.translation.annotation.TranslationType;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
|
||||
@AllArgsConstructor
|
||||
@TranslationType(type = TransConstant.XZD_SJ_CONTRACT_INFO_ID_TO_NAME)
|
||||
public class XzdSjContractInfoImpl implements TranslationInterface<String> {
|
||||
|
||||
private final XzdSjContractInfoService xzdSjContractInfoService;
|
||||
@Override
|
||||
public String translation(Object key, String other) {
|
||||
if (key instanceof String ids) {
|
||||
return xzdSjContractInfoService.selectNameByIds(ids);
|
||||
} else if (key instanceof Long id) {
|
||||
return xzdSjContractInfoService.selectNameByIds(id.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.dromara.common.translation.core.impl;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.dromara.common.core.service.XzdSupplierOpenBankService;
|
||||
import org.dromara.common.translation.annotation.TranslationType;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
|
||||
@AllArgsConstructor
|
||||
@TranslationType(type = TransConstant.XZD_SUPPLIER_OPEN_BANK_ID_TO_NAME)
|
||||
public class XzdSupplierOpenBankImpl implements TranslationInterface<String> {
|
||||
|
||||
private final XzdSupplierOpenBankService xzdSupplierOpenBankService;
|
||||
@Override
|
||||
public String translation(Object key, String other) {
|
||||
if (key instanceof String ids) {
|
||||
return xzdSupplierOpenBankService.selectNameByIds(ids);
|
||||
} else if (key instanceof Long id) {
|
||||
return xzdSupplierOpenBankService.selectNameByIds(id.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -4,3 +4,14 @@ org.dromara.common.translation.core.impl.DictTypeTranslationImpl
|
||||
org.dromara.common.translation.core.impl.OssUrlTranslationImpl
|
||||
org.dromara.common.translation.core.impl.UserNameTranslationImpl
|
||||
org.dromara.common.translation.core.impl.NicknameTranslationImpl
|
||||
org.dromara.common.translation.core.impl.ProjectNameTranslationImpl
|
||||
org.dromara.common.translation.core.impl.XzdCustomerinformationImpl
|
||||
org.dromara.common.translation.core.impl.XzdProjectImpl
|
||||
org.dromara.common.translation.core.impl.XzdPurchaseContractInformationImpl
|
||||
org.dromara.common.translation.core.impl.XzdJsCgJungonImpl
|
||||
org.dromara.common.translation.core.impl.XzdCsContractInformationImpl
|
||||
org.dromara.common.translation.core.impl.XzdSupplierOpenBankImpl
|
||||
org.dromara.common.translation.core.impl.XzdProjectManagerApprovalImpl
|
||||
org.dromara.common.translation.core.impl.XzdCbysZjhcbImpl
|
||||
org.dromara.common.translation.core.impl.XzdJXHTTranslation
|
||||
org.dromara.common.translation.core.impl.XzdSjContractInfoImpl
|
||||
|
||||
@ -13,6 +13,7 @@ import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
|
||||
/**
|
||||
* WebSocket 配置
|
||||
|
||||
@ -12,6 +12,8 @@ public interface WebSocketConstants {
|
||||
*/
|
||||
String LOGIN_USER_KEY = "loginUser";
|
||||
|
||||
String PROJECT_ID = "projectId";
|
||||
|
||||
/**
|
||||
* 订阅的频道
|
||||
*/
|
||||
|
||||
@ -13,6 +13,7 @@ import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.dromara.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY;
|
||||
import static org.dromara.common.websocket.constant.WebSocketConstants.PROJECT_ID;
|
||||
|
||||
/**
|
||||
* WebSocketHandler 实现类
|
||||
@ -27,14 +28,17 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
|
||||
*/
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) throws IOException {
|
||||
LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
|
||||
if (ObjectUtil.isNull(loginUser)) {
|
||||
// LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
|
||||
Long loginUser = (Long) session.getAttributes().get(PROJECT_ID);
|
||||
// if (ObjectUtil.isNull(loginUser) ) {
|
||||
if (loginUser == null ) {
|
||||
session.close(CloseStatus.BAD_DATA);
|
||||
log.info("[connect] invalid token received. sessionId: {}", session.getId());
|
||||
return;
|
||||
}
|
||||
WebSocketSessionHolder.addSession(loginUser.getUserId(), session);
|
||||
log.info("[connect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
|
||||
WebSocketSessionHolder.addSession(loginUser, session);
|
||||
// WebSocketSessionHolder.addSession(loginUser.getUserId(), session);
|
||||
// log.info("[connect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,11 +51,13 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
// 从WebSocket会话中获取登录用户信息
|
||||
LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
|
||||
// LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
|
||||
Long loginUser = (Long) session.getAttributes().get(PROJECT_ID);
|
||||
|
||||
// 创建WebSocket消息DTO对象
|
||||
WebSocketMessageDto webSocketMessageDto = new WebSocketMessageDto();
|
||||
webSocketMessageDto.setSessionKeys(List.of(loginUser.getUserId()));
|
||||
// webSocketMessageDto.setSessionKeys(List.of(loginUser.getUserId()));
|
||||
webSocketMessageDto.setSessionKeys(List.of(loginUser));
|
||||
webSocketMessageDto.setMessage(message.getPayload());
|
||||
WebSocketUtils.publishMessage(webSocketMessageDto);
|
||||
}
|
||||
@ -100,13 +106,16 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
|
||||
*/
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
|
||||
if (ObjectUtil.isNull(loginUser)) {
|
||||
// LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
|
||||
Long loginUser = (Long) session.getAttributes().get(PROJECT_ID);
|
||||
// if (ObjectUtil.isNull(loginUser)) {
|
||||
if (loginUser != null ) {
|
||||
log.info("[disconnect] invalid token received. sessionId: {}", session.getId());
|
||||
return;
|
||||
}
|
||||
WebSocketSessionHolder.removeSession(loginUser.getUserId());
|
||||
log.info("[disconnect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
|
||||
// WebSocketSessionHolder.removeSession(loginUser.getUserId());
|
||||
WebSocketSessionHolder.removeSession(loginUser);
|
||||
// log.info("[disconnect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -15,6 +15,7 @@ import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.dromara.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY;
|
||||
import static org.dromara.common.websocket.constant.WebSocketConstants.PROJECT_ID;
|
||||
|
||||
/**
|
||||
* WebSocket握手请求的拦截器
|
||||
@ -44,6 +45,7 @@ public class PlusWebSocketInterceptor implements HandshakeInterceptor {
|
||||
String headerCid = ServletUtils.getRequest().getHeader(LoginHelper.CLIENT_KEY);
|
||||
String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY);
|
||||
String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
|
||||
String projectIdStr = ServletUtils.getRequest().getParameter(LoginHelper.PROJECT_KEY);
|
||||
if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
|
||||
// token 无效
|
||||
throw NotLoginException.newInstance(StpUtil.getLoginType(),
|
||||
@ -52,6 +54,10 @@ public class PlusWebSocketInterceptor implements HandshakeInterceptor {
|
||||
}
|
||||
|
||||
attributes.put(LOGIN_USER_KEY, loginUser);
|
||||
if (projectIdStr != null && !projectIdStr.isEmpty()) {
|
||||
Long projectId = Long.parseLong(projectIdStr);
|
||||
attributes.put(PROJECT_ID,projectId);
|
||||
}
|
||||
return true;
|
||||
} catch (NotLoginException e) {
|
||||
log.error("WebSocket 认证失败'{}',无法访问系统资源", e.getMessage());
|
||||
|
||||
@ -26,7 +26,7 @@ public class WebSocketTopicListener implements ApplicationRunner, Ordered {
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
// 订阅WebSocket消息
|
||||
WebSocketUtils.subscribeMessage((message) -> {
|
||||
log.info("WebSocket主题订阅收到消息session keys={} message={}", message.getSessionKeys(), message.getMessage());
|
||||
// log.info("WebSocket主题订阅收到消息session keys={} message={}", message.getSessionKeys(), message.getMessage());
|
||||
// 如果key不为空就按照key发消息 如果为空就群发
|
||||
if (CollUtil.isNotEmpty(message.getSessionKeys())) {
|
||||
message.getSessionKeys().forEach(key -> {
|
||||
|
||||
@ -69,8 +69,8 @@ public class WebSocketUtils {
|
||||
broadcastMessage.setMessage(webSocketMessage.getMessage());
|
||||
broadcastMessage.setSessionKeys(unsentSessionKeys);
|
||||
RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> {
|
||||
log.info(" WebSocket发送主题订阅消息topic:{} session keys:{} message:{}",
|
||||
WEB_SOCKET_TOPIC, unsentSessionKeys, webSocketMessage.getMessage());
|
||||
// log.info(" WebSocket发送主题订阅消息topic:{} session keys:{} message:{}",
|
||||
// WEB_SOCKET_TOPIC, unsentSessionKeys, webSocketMessage.getMessage());
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -84,7 +84,7 @@ public class WebSocketUtils {
|
||||
WebSocketMessageDto broadcastMessage = new WebSocketMessageDto();
|
||||
broadcastMessage.setMessage(message);
|
||||
RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> {
|
||||
log.info("WebSocket发送主题订阅消息topic:{} message:{}", WEB_SOCKET_TOPIC, message);
|
||||
// log.info("WebSocket发送主题订阅消息topic:{} message:{}", WEB_SOCKET_TOPIC, message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ snail-job:
|
||||
--- # 监控中心配置
|
||||
spring.boot.admin.client:
|
||||
# 增加客户端开关
|
||||
enabled: true
|
||||
enabled: false
|
||||
url: http://192.168.110.119:9090/admin
|
||||
instance:
|
||||
service-host-type: IP
|
||||
|
||||
@ -2,9 +2,9 @@ spring:
|
||||
datasource:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: root
|
||||
url: jdbc:mysql://192.168.110.2:13386/xinnengyuan?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: xinnengyuan
|
||||
password: mEZPC5Sdf3r2HENi
|
||||
hikari:
|
||||
connection-timeout: 30000
|
||||
validation-timeout: 5000
|
||||
@ -42,8 +42,8 @@ snail-job:
|
||||
--- # 监控中心配置
|
||||
spring.boot.admin.client:
|
||||
# 增加客户端开关
|
||||
enabled: true
|
||||
url: http://localhost:9090/admin
|
||||
enabled: false
|
||||
url: http://192.168.110.2:19191/admin
|
||||
instance:
|
||||
service-host-type: IP
|
||||
metadata:
|
||||
|
||||
@ -15,16 +15,80 @@
|
||||
system系统模块
|
||||
</description>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud.ai</groupId>
|
||||
<artifactId>spring-ai-alibaba-bom</artifactId>
|
||||
<version>1.0.0.2</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
<!-- TwelveMonkeys ImageIO 扩展 -->
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<version>3.12.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-webp</artifactId>
|
||||
<version>3.12.0</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.sejda.imageio/webp-imageio -->
|
||||
<dependency>
|
||||
<groupId>org.sejda.imageio</groupId>
|
||||
<artifactId>webp-imageio</artifactId>
|
||||
<version>0.1.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud.ai</groupId>
|
||||
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.esotericsoftware</groupId>
|
||||
<artifactId>kryo</artifactId>
|
||||
<version>5.6.2</version>
|
||||
</dependency>
|
||||
<!-- Java WebSocket 标准API -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>javax.websocket</groupId>-->
|
||||
<!-- <artifactId>javax.websocket-api</artifactId>-->
|
||||
<!-- <version>1.1</version>-->
|
||||
<!-- <scope>provided</scope>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.drewnoakes</groupId>-->
|
||||
<!-- <artifactId>metadata-extractor</artifactId>-->
|
||||
<!-- <version>2.18.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>technology.tabula</groupId>-->
|
||||
<!-- <artifactId>tabula</artifactId>-->
|
||||
<!-- <version>1.0.4</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>technology.tabula</groupId>-->
|
||||
<!-- <artifactId>tabula</artifactId>-->
|
||||
<!-- <version>1.0.4</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
|
||||
<!-- JSON解析(FastJSON) -->
|
||||
@ -88,19 +152,26 @@
|
||||
<artifactId>layout</artifactId>
|
||||
<version>7.2.5</version>
|
||||
</dependency>
|
||||
<!-- iText 7 核心模块(必须,layout依赖此模块) -->
|
||||
<dependency>
|
||||
<groupId>com.itextpdf</groupId>
|
||||
<artifactId>kernel</artifactId>
|
||||
<version>7.2.5</version> <!-- 与layout版本严格一致 -->
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 支持中文字体 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.itextpdf</groupId>-->
|
||||
<!-- <artifactId>itext-asian</artifactId>-->
|
||||
<!-- <version>5.2.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <!– iText –>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.itextpdf</groupId>-->
|
||||
<!-- <artifactId>itextpdf</artifactId>-->
|
||||
<!-- <version>5.5.13.3</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.itextpdf</groupId>-->
|
||||
<!-- <artifactId>itext-asian</artifactId>-->
|
||||
<!-- <version>5.2.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <!– iText –>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.itextpdf</groupId>-->
|
||||
<!-- <artifactId>itextpdf</artifactId>-->
|
||||
<!-- <version>5.5.13.3</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- ZXing -->
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
@ -244,6 +315,18 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>1.15</version> <!-- 最新版本可自行调整 -->
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
package org.dromara.ai.advisor;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.client.ChatClientMessageAggregator;
|
||||
import org.springframework.ai.chat.client.ChatClientRequest;
|
||||
import org.springframework.ai.chat.client.ChatClientResponse;
|
||||
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
|
||||
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 自定义日志拦截器
|
||||
*
|
||||
* @author lilemy
|
||||
* @date 2025-11-04 10:15
|
||||
*/
|
||||
@Slf4j
|
||||
public class CustomLoggerAdvisor implements CallAdvisor, StreamAdvisor {
|
||||
|
||||
private void before(ChatClientRequest request) {
|
||||
log.info("AI 请求参数:{}", request.prompt());
|
||||
}
|
||||
|
||||
private void observeAfter(ChatClientResponse response) {
|
||||
if (response.chatResponse() != null) {
|
||||
log.info("AI 响应结果:{}", response.chatResponse().getResult().getOutput().getText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
|
||||
before(chatClientRequest);
|
||||
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
|
||||
observeAfter(chatClientResponse);
|
||||
return chatClientResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
|
||||
before(chatClientRequest);
|
||||
Flux<ChatClientResponse> chatClientResponseFlux = streamAdvisorChain.nextStream(chatClientRequest);
|
||||
return (new ChatClientMessageAggregator()).aggregateChatClientResponse(chatClientResponseFlux, this::observeAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the advisor.
|
||||
*
|
||||
* @return the advisor name.
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
package org.dromara.ai.chat;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.advisor.CustomLoggerAdvisor;
|
||||
import org.dromara.ai.chatmemory.FileBasedChatMemory;
|
||||
import org.dromara.ai.domain.AIChatMemory;
|
||||
import org.dromara.ai.service.IAIChatMemoryService;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-11-04 09:34
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DashScopeChat {
|
||||
|
||||
@Resource
|
||||
private DashScopeSimpleChat dashScopeSimpleChat;
|
||||
|
||||
@Resource
|
||||
private IAIChatMemoryService chatMemoryService;
|
||||
|
||||
private final ChatClient chatClient;
|
||||
|
||||
private static final String DEFAULT_PROMPT =
|
||||
"你是一名智能助手,代号为“煤球”。回答时不要提及自身身份或角色设定,除非用户主动询问;请直接提供清晰、准确、专业的回答。";
|
||||
|
||||
private static final String DEFAULT_FILE_DIR = System.getProperty("user.dir") + "/chat-memory";
|
||||
|
||||
public DashScopeChat(ChatModel dashScopeChatModel) {
|
||||
// 初始化基于文件的对话记忆
|
||||
ChatMemory chatMemory = new FileBasedChatMemory(DEFAULT_FILE_DIR);
|
||||
chatClient = ChatClient.builder(dashScopeChatModel)
|
||||
.defaultSystem(DEFAULT_PROMPT)
|
||||
.defaultAdvisors(
|
||||
MessageChatMemoryAdvisor.builder(chatMemory).build(),
|
||||
// 自定义日志输出
|
||||
new CustomLoggerAdvisor()
|
||||
).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 对话,流式输出
|
||||
*
|
||||
* @param message 用户输入
|
||||
* @param chatId 会话id
|
||||
* @return 流式输出结果
|
||||
*/
|
||||
public Flux<String> doChatStream(String message, String chatId, Boolean isFirst) {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
return chatClient
|
||||
.prompt()
|
||||
.user(message)
|
||||
.advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, chatId))
|
||||
.stream()
|
||||
.content()// 收集所有 token,生成完整回复
|
||||
.collectList()
|
||||
.flatMapMany(tokens -> {
|
||||
String aiResponse = String.join("", tokens);
|
||||
// 异步生成标题
|
||||
generateChatTitleAsync(chatId, message, aiResponse, userId);
|
||||
// 返回完整的流结果
|
||||
return Flux.fromIterable(tokens);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步生成标题
|
||||
*
|
||||
* @param chatId 会话id
|
||||
* @param userMessage 用户输入
|
||||
* @param aiResponse AI回复
|
||||
* @param userId 用户id
|
||||
*/
|
||||
private void generateChatTitleAsync(String chatId, String userMessage, String aiResponse, Long userId) {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
// 先判断一下当前聊天是否存在
|
||||
Long count = chatMemoryService.lambdaQuery()
|
||||
.eq(AIChatMemory::getUserId, userId)
|
||||
.eq(AIChatMemory::getFileName, chatId)
|
||||
.count();
|
||||
if (count > 0) {
|
||||
return;
|
||||
}
|
||||
// 构建生成标题的提示词
|
||||
String prompt = String.format("""
|
||||
请以陈述句的形式总结下面这段用户与AI的对话生成一个简短的标题(不超过10个字):
|
||||
用户:%s
|
||||
AI:%s
|
||||
""", userMessage, aiResponse);
|
||||
String title = dashScopeSimpleChat.doChat(prompt);
|
||||
log.info("用户:{} 生成标题成功:{} -> {}", userId, chatId, title);
|
||||
// 保存对话数据
|
||||
AIChatMemory memory = new AIChatMemory();
|
||||
memory.setUserId(userId);
|
||||
memory.setFileName(chatId);
|
||||
memory.setFirstQuestion(title);
|
||||
chatMemoryService.save(memory);
|
||||
} catch (Exception e) {
|
||||
log.error("生成标题失败", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package org.dromara.ai.chat;
|
||||
|
||||
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-11-04 15:26
|
||||
*/
|
||||
@Component
|
||||
public class DashScopeSimpleChat {
|
||||
|
||||
private final ChatClient dashScopeChatClient;
|
||||
|
||||
public DashScopeSimpleChat(ChatClient.Builder chatClientBuilder) {
|
||||
this.dashScopeChatClient = chatClientBuilder
|
||||
// 设置 ChatClient 中 ChatModel 的 Options 参数
|
||||
.defaultOptions(
|
||||
DashScopeChatOptions.builder()
|
||||
.withTopP(0.7)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 对话
|
||||
*
|
||||
* @param message 用户输入
|
||||
* @return 响应结果
|
||||
*/
|
||||
public String doChat(String message) {
|
||||
return dashScopeChatClient.prompt(message).call().content();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
package org.dromara.ai.chatmemory;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.objenesis.strategy.StdInstantiatorStrategy;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 基于文件持久化的对话记忆
|
||||
*/
|
||||
@Slf4j
|
||||
public class FileBasedChatMemory implements ChatMemory {
|
||||
|
||||
private final String BASE_DIR;
|
||||
private static final Kryo kryo = new Kryo();
|
||||
|
||||
static {
|
||||
kryo.setRegistrationRequired(false);
|
||||
// 设置实例化策略
|
||||
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
|
||||
}
|
||||
|
||||
// 构造对象时,指定文件保存目录
|
||||
public FileBasedChatMemory(String dir) {
|
||||
this.BASE_DIR = dir;
|
||||
File baseDir = new File(dir);
|
||||
if (!baseDir.exists()) {
|
||||
baseDir.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(String conversationId, List<Message> messages) {
|
||||
List<Message> conversationMessages = getOrCreateConversation(conversationId);
|
||||
conversationMessages.addAll(messages);
|
||||
saveConversation(conversationId, conversationMessages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Message> get(String conversationId) {
|
||||
List<Message> messages = getOrCreateConversation(conversationId);
|
||||
log.info("获取对话:{}", messages);
|
||||
return messages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(String conversationId) {
|
||||
File file = getConversationFile(conversationId);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Message> getOrCreateConversation(String conversationId) {
|
||||
File file = getConversationFile(conversationId);
|
||||
List<Message> messages = new ArrayList<>();
|
||||
if (file.exists()) {
|
||||
try (Input input = new Input(new FileInputStream(file))) {
|
||||
messages = kryo.readObject(input, ArrayList.class);
|
||||
} catch (IOException e) {
|
||||
log.error("读取对话失败", e);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
private void saveConversation(String conversationId, List<Message> messages) {
|
||||
File file = getConversationFile(conversationId);
|
||||
try (Output output = new Output(new FileOutputStream(file))) {
|
||||
kryo.writeObject(output, messages);
|
||||
} catch (IOException e) {
|
||||
log.error("保存对话失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private File getConversationFile(String conversationId) {
|
||||
return new File(BASE_DIR, conversationId + ".kryo");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package org.dromara.ai.controller;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.chat.DashScopeChat;
|
||||
import org.dromara.ai.chatmemory.FileBasedChatMemory;
|
||||
import org.dromara.ai.domain.dto.AIChatReq;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-10-23 11:32
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/ai/chat")
|
||||
public class AIChatController {
|
||||
|
||||
@Resource
|
||||
private DashScopeChat dashScopeChat;
|
||||
|
||||
/**
|
||||
* ChatClient 流式调用
|
||||
*/
|
||||
@GetMapping("/stream")
|
||||
public Flux<String> streamChat(@Validated AIChatReq req, HttpServletResponse response) {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
return dashScopeChat.doChatStream(req.getQuery(), req.getChatId(), req.getIsFirst());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话记录
|
||||
*/
|
||||
@GetMapping("/history")
|
||||
public R<List<Message>> getChatHistory(String chatId) {
|
||||
FileBasedChatMemory memory = new FileBasedChatMemory(System.getProperty("user.dir") + "/chat-memory");
|
||||
return R.ok(memory.get(chatId));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
package org.dromara.ai.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryQueryReq;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryUpdateReq;
|
||||
import org.dromara.ai.domain.vo.AIChatMemoryVo;
|
||||
import org.dromara.ai.service.IAIChatMemoryService;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI 对话记录信息
|
||||
*
|
||||
* @author lilemy
|
||||
* @date 2025-11-04
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/chatMemory")
|
||||
public class AIChatMemoryController extends BaseController {
|
||||
|
||||
private final IAIChatMemoryService aiChatMemoryService;
|
||||
|
||||
/**
|
||||
* 查询AI 对话记录信息列表
|
||||
*/
|
||||
@SaCheckPermission("ai:chatMemory:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AIChatMemoryVo> list(AIChatMemoryQueryReq req, PageQuery pageQuery) {
|
||||
return aiChatMemoryService.queryPageList(req, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出AI 对话记录信息列表
|
||||
*/
|
||||
@SaCheckPermission("ai:chatMemory:export")
|
||||
@Log(title = "AI 对话记录信息", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(AIChatMemoryQueryReq req, HttpServletResponse response) {
|
||||
List<AIChatMemoryVo> list = aiChatMemoryService.queryList(req);
|
||||
ExcelUtil.exportExcel(list, "AI 对话记录信息", AIChatMemoryVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI 对话记录信息详细信息
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
@SaCheckPermission("ai:chatMemory:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AIChatMemoryVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiChatMemoryService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 AI 对话记录信息
|
||||
*/
|
||||
@SaCheckPermission("ai:chatMemory:edit")
|
||||
@Log(title = "AI 对话记录信息", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> updateAIChatMemory(@Validated AIChatMemoryUpdateReq req) {
|
||||
return toAjax(aiChatMemoryService.updateByReq(req));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除AI 对话记录信息
|
||||
*
|
||||
* @param ids 主键串
|
||||
*/
|
||||
@SaCheckPermission("ai:chatMemory:remove")
|
||||
@Log(title = "AI 对话记录信息", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(aiChatMemoryService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package org.dromara.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* AI 对话记录信息对象 ai_chat_memory
|
||||
*
|
||||
* @author lilemy
|
||||
* @date 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("ai_chat_memory")
|
||||
public class AIChatMemory extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
private String fileName;
|
||||
|
||||
/**
|
||||
* 第一条问题
|
||||
*/
|
||||
private String firstQuestion;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package org.dromara.ai.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-11-04 15:19
|
||||
*/
|
||||
@Data
|
||||
public class AIChatMemoryQueryReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -4090176451164739134L;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
private String fileName;
|
||||
|
||||
/**
|
||||
* 第一条问题
|
||||
*/
|
||||
private String firstQuestion;
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package org.dromara.ai.domain.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-11-05 11:28
|
||||
*/
|
||||
@Data
|
||||
public class AIChatMemoryUpdateReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 6541297164616819137L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@NotNull(message = "主键不能为空")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 第一条问题
|
||||
*/
|
||||
private String firstQuestion;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package org.dromara.ai.domain.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-11-05 09:31
|
||||
*/
|
||||
@Data
|
||||
public class AIChatReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -4669223630531267889L;
|
||||
|
||||
/**
|
||||
* 聊天内容
|
||||
*/
|
||||
@NotBlank(message = "请输入内容")
|
||||
private String query;
|
||||
|
||||
/**
|
||||
* 会话id
|
||||
*/
|
||||
@NotBlank(message = "请输入会话id")
|
||||
private String chatId;
|
||||
|
||||
/**
|
||||
* 是否首次对话
|
||||
*/
|
||||
@NotNull(message = "请选择是否首次对话")
|
||||
private Boolean isFirst;
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package org.dromara.ai.domain.vo;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.dromara.ai.domain.AIChatMemory;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/**
|
||||
* AI 对话记录信息视图对象 ai_chat_memory
|
||||
*
|
||||
* @author lilemy
|
||||
* @date 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = AIChatMemory.class)
|
||||
public class AIChatMemoryVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@ExcelProperty(value = "主键")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@ExcelProperty(value = "用户id")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
@ExcelProperty(value = "文件名")
|
||||
private String fileName;
|
||||
|
||||
/**
|
||||
* 第一条问题
|
||||
*/
|
||||
@ExcelProperty(value = "第一条问题")
|
||||
private String firstQuestion;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@ExcelProperty(value = "备注")
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package org.dromara.ai.mapper;
|
||||
|
||||
import org.dromara.ai.domain.AIChatMemory;
|
||||
import org.dromara.ai.domain.vo.AIChatMemoryVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* AI 对话记录信息Mapper接口
|
||||
*
|
||||
* @author lilemy
|
||||
* @date 2025-11-04
|
||||
*/
|
||||
public interface AIChatMemoryMapper extends BaseMapperPlus<AIChatMemory, AIChatMemoryVo> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
package org.dromara.ai.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.dromara.ai.domain.AIChatMemory;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryQueryReq;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryUpdateReq;
|
||||
import org.dromara.ai.domain.vo.AIChatMemoryVo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI 对话记录信息Service接口
|
||||
*
|
||||
* @author lilemy
|
||||
* @date 2025-11-04
|
||||
*/
|
||||
public interface IAIChatMemoryService extends IService<AIChatMemory> {
|
||||
|
||||
/**
|
||||
* 查询AI 对话记录信息
|
||||
*
|
||||
* @param id 主键
|
||||
* @return AI 对话记录信息
|
||||
*/
|
||||
AIChatMemoryVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 分页查询AI 对话记录信息列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return AI 对话记录信息分页列表
|
||||
*/
|
||||
TableDataInfo<AIChatMemoryVo> queryPageList(AIChatMemoryQueryReq bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询符合条件的AI 对话记录信息列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return AI 对话记录信息列表
|
||||
*/
|
||||
List<AIChatMemoryVo> queryList(AIChatMemoryQueryReq bo);
|
||||
|
||||
/**
|
||||
* 修改 AI 对话记录信息
|
||||
*
|
||||
* @param req 修改参数
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
Boolean updateByReq(AIChatMemoryUpdateReq req);
|
||||
|
||||
/**
|
||||
* 校验并批量删除AI 对话记录信息信息
|
||||
*
|
||||
* @param ids 待删除的主键集合
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
}
|
||||
@ -0,0 +1,150 @@
|
||||
package org.dromara.ai.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.domain.AIChatMemory;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryQueryReq;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryUpdateReq;
|
||||
import org.dromara.ai.domain.vo.AIChatMemoryVo;
|
||||
import org.dromara.ai.mapper.AIChatMemoryMapper;
|
||||
import org.dromara.ai.service.IAIChatMemoryService;
|
||||
import org.dromara.common.core.constant.HttpStatus;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.file.FileUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI 对话记录信息Service业务层处理
|
||||
*
|
||||
* @author lilemy
|
||||
* @date 2025-11-04
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AIChatMemoryServiceImpl extends ServiceImpl<AIChatMemoryMapper, AIChatMemory>
|
||||
implements IAIChatMemoryService {
|
||||
|
||||
/**
|
||||
* 查询AI 对话记录信息
|
||||
*
|
||||
* @param id 主键
|
||||
* @return AI 对话记录信息
|
||||
*/
|
||||
@Override
|
||||
public AIChatMemoryVo queryById(Long id) {
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询AI 对话记录信息列表
|
||||
*
|
||||
* @param req 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return AI 对话记录信息分页列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<AIChatMemoryVo> queryPageList(AIChatMemoryQueryReq req, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<AIChatMemory> lqw = buildQueryWrapper(req);
|
||||
Page<AIChatMemoryVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询符合条件的AI 对话记录信息列表
|
||||
*
|
||||
* @param req 查询条件
|
||||
* @return AI 对话记录信息列表
|
||||
*/
|
||||
@Override
|
||||
public List<AIChatMemoryVo> queryList(AIChatMemoryQueryReq req) {
|
||||
LambdaQueryWrapper<AIChatMemory> lqw = buildQueryWrapper(req);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 AI 对话记录信息
|
||||
*
|
||||
* @param req 修改参数
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByReq(AIChatMemoryUpdateReq req) {
|
||||
AIChatMemory oldChatMemory = this.getById(req.getId());
|
||||
if (oldChatMemory == null) {
|
||||
throw new ServiceException("数据不存在", HttpStatus.NOT_FOUND);
|
||||
}
|
||||
AIChatMemory update = new AIChatMemory();
|
||||
BeanUtils.copyProperties(req, update);
|
||||
return this.updateById(update);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<AIChatMemory> buildQueryWrapper(AIChatMemoryQueryReq req) {
|
||||
LambdaQueryWrapper<AIChatMemory> lqw = Wrappers.lambdaQuery();
|
||||
lqw.orderByDesc(AIChatMemory::getId);
|
||||
lqw.eq(req.getUserId() != null, AIChatMemory::getUserId, req.getUserId());
|
||||
lqw.like(StringUtils.isNotBlank(req.getFileName()), AIChatMemory::getFileName, req.getFileName());
|
||||
lqw.eq(StringUtils.isNotBlank(req.getFirstQuestion()), AIChatMemory::getFirstQuestion, req.getFirstQuestion());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验并批量删除AI 对话记录信息信息
|
||||
*
|
||||
* @param ids 待删除的主键集合
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
List<AIChatMemory> aiChatMemories = this.listByIds(ids);
|
||||
if (aiChatMemories.size() != ids.size()) {
|
||||
throw new ServiceException("数据不存在");
|
||||
}
|
||||
// 暂存文件路径
|
||||
List<File> filesToDelete = new ArrayList<>();
|
||||
for (AIChatMemory mem : aiChatMemories) {
|
||||
if (StringUtils.isNotBlank(mem.getFileName())) {
|
||||
File file = new File(System.getProperty("user.dir")
|
||||
+ "/chat-memory/", mem.getFileName());
|
||||
|
||||
try {
|
||||
// 防止目录穿越
|
||||
if (file.getCanonicalPath().startsWith(
|
||||
new File(System.getProperty("user.dir") + "/chat-memory").getCanonicalPath())) {
|
||||
filesToDelete.add(file);
|
||||
} else {
|
||||
log.warn("危险的 filename 路径被拒绝删除: {}", mem.getFileName());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("filename 获取失败: {}", mem.getFileName());
|
||||
throw new ServiceException("文件获取失败: " + mem.getFileName());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 删除数据库
|
||||
boolean b = baseMapper.deleteByIds(ids) > 0;
|
||||
if (!b) {
|
||||
throw new ServiceException("数据库删除失败");
|
||||
}
|
||||
// 删除文件(非事务,不影响数据库逻辑)
|
||||
for (File f : filesToDelete) {
|
||||
if (f.exists() && !FileUtils.del(f)) {
|
||||
log.warn("文件删除失败: {}", f.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,157 @@
|
||||
package org.dromara.app.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.app.domain.SysPackage;
|
||||
import org.dromara.app.domain.bo.SysPackageBo;
|
||||
import org.dromara.app.domain.vo.SysPackageVo;
|
||||
import org.dromara.app.service.SysPackageServiceImpl;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.system.domain.vo.SysOssVo;
|
||||
import org.dromara.system.service.impl.SysOssServiceImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/app/sysPackage")
|
||||
public class SysPackageController {
|
||||
|
||||
@Autowired
|
||||
private SysOssServiceImpl sysOssService;
|
||||
@Autowired
|
||||
private SysPackageServiceImpl sysPackageService;
|
||||
|
||||
/**
|
||||
* 获取列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<SysPackageVo> list(SysPackageBo bo, PageQuery pageQuery) {
|
||||
return sysPackageService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新版本
|
||||
*/
|
||||
@GetMapping("/getNewVersion")
|
||||
public R<SysPackage> getNewVersion() {
|
||||
LambdaQueryWrapper<SysPackage> lambdaQueryWrapper =new LambdaQueryWrapper<>();
|
||||
lambdaQueryWrapper.orderByDesc(SysPackage::getCreateTime);
|
||||
lambdaQueryWrapper.last("LIMIT 1");
|
||||
SysPackage one = sysPackageService.getOne(lambdaQueryWrapper);
|
||||
return R.ok(one);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传最新安装包及版本
|
||||
*/
|
||||
@GetMapping("/uploadNewVersion")
|
||||
public R<SysPackage> uploadNewVersion(String version, String type, MultipartFile file, String context) {
|
||||
String originalFileName = "apk/package/app-release.apk";
|
||||
|
||||
// 先查询最新记录
|
||||
LambdaQueryWrapper<SysPackage> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||
lambdaQueryWrapper.orderByDesc(SysPackage::getCreateTime);
|
||||
List<SysPackage> list = sysPackageService.list(lambdaQueryWrapper);
|
||||
|
||||
// 分离事务:先处理文件上传
|
||||
SysOssVo upload = sysOssService.upload(file, originalFileName);
|
||||
if (upload == null) {
|
||||
return R.fail("上传失败");
|
||||
}
|
||||
|
||||
// 分离事务:再处理数据库操作
|
||||
return handleDatabaseOperations(version, type, upload, list, context);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public R<SysPackage> handleDatabaseOperations(String version, String type, SysOssVo upload, List<SysPackage> existingPackages, String context) {
|
||||
try {
|
||||
// 先删除旧文件记录
|
||||
if (existingPackages != null && !existingPackages.isEmpty()) {
|
||||
SysPackage first = existingPackages.getFirst();
|
||||
Boolean b = sysOssService.deleteWithValidByIds(List.of(first.getFileId()), false);
|
||||
if (!b) {
|
||||
log.error("通过IDS删除文件失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 保存新记录
|
||||
SysPackage sysPackage = new SysPackage();
|
||||
sysPackage.setVersion(version);
|
||||
sysPackage.setFileId(upload.getOssId());
|
||||
sysPackage.setFileUrl(upload.getUrl());
|
||||
sysPackage.setType(type);
|
||||
sysPackage.setContext(context);
|
||||
|
||||
boolean save = sysPackageService.save(sysPackage);
|
||||
if (!save) {
|
||||
return R.fail("保存失败");
|
||||
}
|
||||
|
||||
return R.ok(sysPackage);
|
||||
} catch (Exception e) {
|
||||
log.error("数据库操作失败", e);
|
||||
throw e; // 重新抛出异常触发事务回滚
|
||||
}
|
||||
}
|
||||
// public R<SysPackage> uploadNewVersion(String version, String type, MultipartFile file) {
|
||||
// String originalFileName = "apk/package/app-release.apk";
|
||||
//
|
||||
// //覆盖失败,先删除文件
|
||||
// LambdaQueryWrapper<SysPackage> lambdaQueryWrapper =new LambdaQueryWrapper<>();
|
||||
// lambdaQueryWrapper.orderByDesc(SysPackage::getCreateTime);
|
||||
// List<SysPackage> list = sysPackageService.list(lambdaQueryWrapper);
|
||||
// if (list != null && !list.isEmpty()){
|
||||
// SysPackage first = list.getFirst();
|
||||
// Boolean b = sysOssService.deleteWithValidByIds(List.of(first.getFileId()), false);
|
||||
// if (!b){
|
||||
// log.error("通过IDS删除文件失败");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// SysOssVo upload = sysOssService.upload(file, originalFileName);
|
||||
// if (upload == null){
|
||||
// return R.fail("上传失败");
|
||||
// }
|
||||
// SysPackage sysPackage = new SysPackage();
|
||||
// sysPackage.setVersion( version);
|
||||
// sysPackage.setFileId(upload.getOssId());
|
||||
// sysPackage.setFileUrl(upload.getUrl());
|
||||
// sysPackage.setType(type);
|
||||
//
|
||||
// boolean save = sysPackageService.save(sysPackage);
|
||||
// if (!save){
|
||||
// return R.fail("保存失败");
|
||||
// }
|
||||
//
|
||||
// return R.ok(sysPackage);
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* 删除 现在指向的是同一个文件
|
||||
*/
|
||||
@Transactional
|
||||
@GetMapping("/remove")
|
||||
public R<Void> remove(Long id){
|
||||
SysPackage byId = sysPackageService.getById(id);
|
||||
Boolean b1 = sysOssService.deleteWithValidByIds(List.of(byId.getFileId()), false);
|
||||
boolean b = sysPackageService.removeById(id);
|
||||
if (b1 && b){
|
||||
return R.ok();
|
||||
}
|
||||
return R.fail("删除文件或记录失败");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package org.dromara.app.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@Data
|
||||
@TableName("sys_package")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class SysPackage extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 0安卓1苹果2鸿蒙
|
||||
*/
|
||||
private String type;
|
||||
|
||||
private String version;
|
||||
|
||||
private Long fileId;
|
||||
|
||||
private String fileUrl;
|
||||
|
||||
/**
|
||||
* 更新内容
|
||||
*/
|
||||
private String context;
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package org.dromara.app.domain.bo;
|
||||
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
import org.dromara.xzd.domain.XzdContractDetails;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@AutoMapper(target = XzdContractDetails.class, reverseConvertGenerate = false)
|
||||
public class SysPackageBo extends BaseEntity {
|
||||
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 0安卓1苹果2鸿蒙
|
||||
*/
|
||||
private String type;
|
||||
|
||||
private String version;
|
||||
|
||||
private Long fileId;
|
||||
|
||||
private String fileUrl;
|
||||
|
||||
/**
|
||||
* 更新内容
|
||||
*/
|
||||
private String context;
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package org.dromara.app.domain.vo;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.dromara.app.domain.SysPackage;
|
||||
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = SysPackage.class)
|
||||
public class SysPackageVo {
|
||||
|
||||
@ExcelProperty("id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 0安卓1苹果2鸿蒙
|
||||
*/
|
||||
@ExcelProperty("安装包类型(0安卓1苹果2鸿蒙)")
|
||||
private String type;
|
||||
|
||||
@ExcelProperty("版本")
|
||||
private String version;
|
||||
|
||||
private Long fileId;
|
||||
|
||||
@ExcelProperty("安装包地址")
|
||||
private String fileUrl;
|
||||
|
||||
/**
|
||||
* 更新内容
|
||||
*/
|
||||
private String context;
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package org.dromara.app.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.dromara.app.domain.SysPackage;
|
||||
import org.dromara.app.domain.vo.SysPackageVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
@Mapper
|
||||
public interface SysPackageMapper extends BaseMapperPlus<SysPackage, SysPackageVo> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package org.dromara.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.dromara.app.domain.SysPackage;
|
||||
import org.dromara.app.domain.bo.SysPackageBo;
|
||||
import org.dromara.app.domain.vo.SysPackageVo;
|
||||
import org.dromara.app.mapper.SysPackageMapper;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SysPackageServiceImpl extends ServiceImpl<SysPackageMapper, SysPackage> {
|
||||
|
||||
public TableDataInfo<SysPackageVo> queryPageList(SysPackageBo bo, PageQuery pageQuery) {
|
||||
// LambdaQueryWrapper<SysPackage> lqw = buildQueryWrapper(bo);
|
||||
Page<SysPackageVo> result = baseMapper.selectVoPage(pageQuery.build(), new LambdaQueryWrapper<>());
|
||||
// setValue(result.getRecords());
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
}
|
||||
@ -107,8 +107,8 @@ public class BusBiddingLimitListController extends BaseController {
|
||||
@Log(title = "成本-投标", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/importExcelFile")
|
||||
public R<Void> importExcelFile(Long projectId, @RequestParam("file") MultipartFile file) {
|
||||
return toAjax(busBiddingLimitListService.importExcelFile(projectId, file));
|
||||
public R<Void> importExcelFile(BusBiddingLimitListBo bo, @RequestParam("file") MultipartFile file) {
|
||||
return toAjax(busBiddingLimitListService.importExcelFile(bo, file));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -2,6 +2,7 @@ package org.dromara.bidding.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
@ -50,7 +51,7 @@ public class BusBiddingUserController extends BaseController {
|
||||
/**
|
||||
* 查询招投标人员列表
|
||||
*/
|
||||
@SaCheckPermission("bidding:biddingUser:getUser")
|
||||
@SaCheckPermission(value = {"bidding:biddingUser:list","bidding:biddingUser:query","bidding:biddingUser:add"},mode = SaMode.OR)
|
||||
@GetMapping("/getUser")
|
||||
public R<List<SysUser>> getUser() {
|
||||
return R.ok(busBiddingUserService.getUser());
|
||||
|
||||
@ -42,6 +42,14 @@ public class BusBiddingLimitVersions extends BaseEntity {
|
||||
*/
|
||||
private String versions;
|
||||
|
||||
|
||||
/**
|
||||
* 版本号名称
|
||||
*/
|
||||
private String versionsName;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* excel文件
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user