mirror of
				https://github.com/dromara/RuoYi-Vue-Plus.git
				synced 2025-10-22 18:03:44 +08:00 
			
		
		
		
	Compare commits
	
		
			862 Commits
		
	
	
		
			v5.1.2
			...
			3761473967
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 3761473967 | ||
|  | 34031cae8d | ||
|  | 26abb98747 | ||
|  | c2f67b4a77 | ||
|  | 4d925a4d62 | ||
|  | 4e62054bd1 | ||
|  | fe40d7db32 | ||
|  | c0eeafb5cd | ||
|  | 5626b97a19 | ||
|  | 0f95e9393d | ||
|  | 5de1ffff90 | ||
|  | 33698ee448 | ||
|  | 5e5d478cf2 | ||
|  | 778096d100 | ||
|  | bba163f7b4 | ||
|  | d4f792810e | ||
|  | d14ee59912 | ||
|  | be5d69d5a5 | ||
|  | 97c36674e4 | ||
|  | 1f1564fad9 | ||
|  | c79e053bea | ||
|  | 92e9ed771b | ||
|  | 800c6c8ff3 | ||
|  | 865627fdad | ||
|  | 192537672e | ||
|  | 384f9528e7 | ||
|  | 5fc76b6426 | ||
|  | 4d8a45204c | ||
|  | 2de9397db8 | ||
|  | bfc73ed214 | ||
|  | 8bf741fd5b | ||
|  | 460545a75e | ||
|  | a93b30ec91 | ||
|  | 34bac1add9 | ||
|  | f028cb76fc | ||
|  | 5a4be5fba1 | ||
|  | 23245b78ca | ||
|  | 13ac302525 | ||
|  | 96a62a3564 | ||
|  | 7adf702283 | ||
|  | 279c8e014a | ||
|  | b7517cbbd4 | ||
|  | 45eac02f4f | ||
|  | a6b7c3afe6 | ||
|  | e99e4f6c58 | ||
|  | bcb97bc406 | ||
|  | ad01406fc1 | ||
|  | 15eb08c065 | ||
|  | 2340556091 | ||
|  | 65c54184e8 | ||
|  | 9dcb7c6a12 | ||
|  | 0c6faa751a | ||
|  | b465cb34de | ||
|  | 21c12a791a | ||
|  | 723a0b6d9c | ||
|  | c4ef053958 | ||
|  | 055d1f3bb2 | ||
|  | fe27d8920a | ||
|  | df65670d3d | ||
|  | 2623d0b343 | ||
|  | c0e0b41d13 | ||
|  | 8763bfa3d3 | ||
|  | 71180584da | ||
|  | 319a89e320 | ||
|  | 0673cf8849 | ||
|  | b537899e62 | ||
|  | 7b679e60e0 | ||
|  | bb475a6088 | ||
|  | a217c495d1 | ||
|  | bdb86e2b3a | ||
|  | e8700ac44b | ||
|  | d80f6ab695 | ||
|  | 381be5a1a1 | ||
|  | 214cbac9a6 | ||
|  | 906a031172 | ||
|  | 236dd6e054 | ||
|  | eb17eb6559 | ||
|  | 2746af21f0 | ||
|  | 78abb617ce | ||
|  | 3c57c0e7f9 | ||
|  | 934bbe8bd7 | ||
|  | 718a010c0f | ||
|  | a87071b834 | ||
|  | 2c598f93ab | ||
|  | 0937093851 | ||
|  | b528f0bd14 | ||
|  | 7c2c82fc0a | ||
|  | ffe8b16ff3 | ||
|  | ecf7ebad53 | ||
|  | 3bf26cd509 | ||
|  | a671d4a8a8 | ||
|  | d9713d0f8c | ||
|  | aeaa33ebd3 | ||
|  | c64de03d27 | ||
|  | 2d99304396 | ||
|  | a22dc9537f | ||
|  | 6c28f8a0dd | ||
|  | c100168374 | ||
|  | 8636d8b3e8 | ||
|  | 37b2d648b1 | ||
|  | 27b4992f6e | ||
|  | 001297ca7a | ||
|  | 4f3cbc4bc2 | ||
|  | 008e02a406 | ||
|  | e8acfac091 | ||
|  | e1a26b0388 | ||
|  | a6fc47b4f6 | ||
|  | ce7f5121b0 | ||
|  | b38ca837d6 | ||
|  | a2714fb9f7 | ||
|  | 8c57d694c5 | ||
|  | 0ae521a7dc | ||
|  | 79ec3fd2c9 | ||
|  | 69b95b3e7a | ||
|  | 9d8b9fabbe | ||
|  | 875c9fa77c | ||
|  | 6bc2d9d4a7 | ||
|  | fe5a1f358d | ||
|  | 1972537176 | ||
|  | 8f21e9e2fd | ||
|  | 089a79002f | ||
|  | ece1dee990 | ||
|  | 652f5161a9 | ||
|  | 4ba4ea4fcc | ||
|  | 3444b50da6 | ||
|  | 62b7d96551 | ||
|  | cb71df8a42 | ||
|  | dd54cc972a | ||
|  | 6f14c91d30 | ||
|  | 6f0dd8dc89 | ||
|  | 7d82f954ac | ||
|  | 69130a76e4 | ||
|  | 9086d32bee | ||
|  | a680310f80 | ||
|  | a07880e1d0 | ||
|  | ae584d54a6 | ||
|  | e36e8f7758 | ||
|  | b47798ef19 | ||
|  | 55b1a67637 | ||
|  | d2b9cd2797 | ||
|  | 9a95c46578 | ||
|  | a58f72868a | ||
|  | 820db87604 | ||
|  | 254e61ab01 | ||
|  | ad53965626 | ||
|  | 4de9fa33b7 | ||
|  | 08e40b611b | ||
|  | 8bd2e27653 | ||
|  | d023510f7e | ||
|  | ec5ca0a08f | ||
|  | a46c798e01 | ||
|  | 41a3bdf73d | ||
|  | a7b83672ba | ||
|  | c2746c2392 | ||
|  | 492e7dab26 | ||
|  | 5480e419b6 | ||
|  | 251a617ecc | ||
|  | 36de389fa4 | ||
|  | f4f052deb4 | ||
|  | 3a0fbd45ae | ||
|  | f20c271972 | ||
|  | ad85fa2016 | ||
|  | 1a403361c9 | ||
|  | 9ad64521d3 | ||
|  | ff76df9ae0 | ||
|  | da1cd55c1d | ||
|  | a65baf5d67 | ||
|  | 489cb52976 | ||
|  | 591f86e4e9 | ||
|  | 9768023d38 | ||
|  | 60af92ed2d | ||
|  | 4d566071db | ||
|  | aface5ded1 | ||
|  | 7f6b71d938 | ||
|  | c8ed71d010 | ||
|  | df6649907d | ||
|  | 2fb1c99f56 | ||
|  | fcebda8987 | ||
|  | 0185a468bd | ||
|  | c566f2ae28 | ||
|  | 29c5ff89ba | ||
|  | ab3e4978b1 | ||
|  | 0027f671d2 | ||
|  | 06a8ab0ab2 | ||
|  | 4352b3fe4a | ||
|  | dd17246086 | ||
|  | d7b0dc91d5 | ||
|  | 4598d4d843 | ||
|  | 8a731efe0d | ||
|  | 59fd7eeeb3 | ||
|  | 552e543471 | ||
|  | b1badca062 | ||
|  | 9f34edee4f | ||
|  | d257de7882 | ||
|  | 4821902fdc | ||
|  | a1f82a7d08 | ||
|  | 9009a90ef2 | ||
|  | fc9c90ad99 | ||
|  | 9e613488f1 | ||
|  | 5e6cb0dd3c | ||
|  | 2bb787886d | ||
|  | ed6f8262c6 | ||
|  | 64d574cf06 | ||
|  | e0b0ffcb28 | ||
|  | c17225abb1 | ||
|  | 19aed0a1e4 | ||
|  | 14c1bde958 | ||
|  | eed929b9fe | ||
|  | eda67dd572 | ||
|  | fef2d5207b | ||
|  | 87294b41af | ||
|  | 99d9c516fc | ||
|  | 476c7a77c8 | ||
|  | 8dc4b5cf6b | ||
|  | eba6b48daf | ||
|  | 1aff46bc1c | ||
|  | e78684886d | ||
|  | 7f35925794 | ||
|  | 71a59d3e5f | ||
|  | 4af46a6045 | ||
|  | d194b39e57 | ||
|  | cd08f66c59 | ||
|  | ef919b9f3d | ||
|  | 847b158283 | ||
|  | dd2abd95c9 | ||
|  | b5908d52d7 | ||
|  | bf515042d0 | ||
|  | e94fccc784 | ||
|  | 6d45199592 | ||
|  | 57b3b329a8 | ||
|  | a0fc268bb9 | ||
|  | 631739733f | ||
|  | 2d2bd48963 | ||
|  | f6d9bec16c | ||
|  | 0f1118e03a | ||
|  | 009ac75229 | ||
|  | b64ac8d7f6 | ||
|  | 41240fc415 | ||
|  | a18e430056 | ||
|  | 1a993a7899 | ||
|  | d3b5220dc3 | ||
|  | 4f92c0317e | ||
|  | a098565c37 | ||
|  | 0d5fe5d91e | ||
|  | 5c66f3b90c | ||
|  | 63d22b9b33 | ||
|  | b4678b74ab | ||
|  | a82ed1e9dd | ||
|  | d2ffbfb80b | ||
|  | 5d61782a6c | ||
|  | 1b8c9fdaa9 | ||
|  | 90328ae79b | ||
|  | 9cdcbbccbf | ||
|  | b820a98c6c | ||
|  | 8e749c472a | ||
|  | 70a5077291 | ||
|  | 8a5d8cc9b9 | ||
|  | 0a0a16f969 | ||
|  | 73d5fbd085 | ||
|  | 9da3d25292 | ||
|  | 0385a444c2 | ||
|  | 7d856b030b | ||
|  | 8c0441b91a | ||
|  | baef55ae1b | ||
|  | 80e6943d2e | ||
|  | 2ffdd56301 | ||
|  | 53635da552 | ||
|  | 5e7fb88762 | ||
|  | d89727725b | ||
|  | 8cd7e3c924 | ||
|  | 0baf2c5861 | ||
|  | b0548f9a56 | ||
|  | 2d190cfb19 | ||
|  | 3b5858b114 | ||
|  | 112157ade0 | ||
|  | 8cd30ae86b | ||
|  | 9b6b288e73 | ||
|  | 1757e5519d | ||
|  | a21fa666fd | ||
|  | df9a57c379 | ||
|  | 5f31efd33e | ||
|  | 2a9f245b39 | ||
|  | 761586cc3c | ||
|  | 6731b7947b | ||
|  | ddc8bd1139 | ||
|  | 6ae9bbdb31 | ||
|  | 6c950c9569 | ||
|  | 3ce3ffca05 | ||
|  | d3ccc43d68 | ||
|  | 4c96440686 | ||
|  | 8d8d76364b | ||
|  | 82af98c205 | ||
|  | fdbe8c2395 | ||
|  | 59715b1e02 | ||
|  | f2ec530065 | ||
|  | ac89cb46f5 | ||
|  | abfa995a32 | ||
|  | 51cfbef887 | ||
|  | 7171ed1508 | ||
|  | 6727f3e6a4 | ||
|  | 81006c758a | ||
|  | 01025c4024 | ||
|  | 517277132e | ||
|  | dc5c92ee2d | ||
|  | 57e142e160 | ||
|  | c563afed59 | ||
|  | 556cf87f0e | ||
|  | e0a00cfe98 | ||
|  | d60774a7b8 | ||
|  | e3d40b75cb | ||
|  | eb4479e940 | ||
|  | 7e6d0a5c64 | ||
|  | 88dd4165cb | ||
|  | e96118c574 | ||
|  | 39c4e5fe55 | ||
|  | 46141dc114 | ||
|  | e3a25f2425 | ||
|  | 64289c16f3 | ||
|  | 6b05ddb576 | ||
|  | 88ee252fce | ||
|  | 2add7291ab | ||
|  | 501be029c6 | ||
|  | d86652dee1 | ||
|  | 272ca613ee | ||
|  | 74af811a3b | ||
|  | fc72b67090 | ||
|  | e33f76d710 | ||
|  | 1c3d594947 | ||
|  | 64d9b27310 | ||
|  | f3f3593cfe | ||
|  | e5e8e3ce7c | ||
|  | a7fd7ba72c | ||
|  | 020f090f4a | ||
|  | 5e3231d59b | ||
|  | b522bc015d | ||
|  | 6e64fd7fd7 | ||
|  | 8b44f5cdbc | ||
|  | f9b7d955aa | ||
|  | 6ea2a2fc51 | ||
|  | 760c8d7200 | ||
|  | 32ad28c3dc | ||
|  | 6886e9fd5b | ||
|  | f20130d3db | ||
|  | df9cc881f1 | ||
|  | 4044988afa | ||
|  | d3360e81b9 | ||
|  | df070b7d78 | ||
|  | 83dd98faf3 | ||
|  | 37f89f560f | ||
|  | 918ed0d6d0 | ||
|  | a3c9edde78 | ||
|  | cac0a4cd16 | ||
|  | 581b6e03d5 | ||
|  | 801cc584e5 | ||
|  | b82ff8617e | ||
|  | c87016c1af | ||
|  | 463048e017 | ||
|  | 79aee1d312 | ||
|  | eb038e91dd | ||
|  | 308c22f922 | ||
|  | 457e59e61c | ||
|  | a964ccbd10 | ||
|  | 0af532f4f1 | ||
|  | 4743eb1d3b | ||
|  | 23aafe1cfe | ||
|  | 3f2499feac | ||
|  | fb97649151 | ||
|  | b33b645ef0 | ||
|  | 9318f182b0 | ||
|  | facbb7f28f | ||
|  | 4de45ce170 | ||
|  | 96d57bd263 | ||
|  | bb4587fe05 | ||
|  | 19c83f02aa | ||
|  | 0e1fcbfe9c | ||
|  | eda882433a | ||
|  | e6847605cc | ||
|  | 5bdffdb368 | ||
|  | 0ad52b18b8 | ||
|  | 77f44574c0 | ||
|  | ca06a2311d | ||
|  | b8d9af65e2 | ||
|  | bc2b4876b6 | ||
|  | 23b70ca0be | ||
|  | 44d776a76f | ||
|  | f03c00b2c1 | ||
|  | 7f60ba9888 | ||
|  | 31569646b0 | ||
|  | 3fc37d6362 | ||
|  | 6d28072167 | ||
|  | f124fbd6aa | ||
|  | 88a4a51956 | ||
|  | 4306ea4181 | ||
|  | e19140462d | ||
|  | 20cc8a6d6c | ||
|  | a9d7a42c65 | ||
|  | f51e6d81b1 | ||
|  | f119d082cf | ||
|  | ecfaa9ad5c | ||
|  | f32d0266ee | ||
|  | 7393a61305 | ||
|  | 2b0efd1f93 | ||
|  | b615a3b088 | ||
|  | 85403e975f | ||
|  | 615ad918ca | ||
|  | b886f3a04b | ||
|  | 588a47897a | ||
|  | 2869d590e6 | ||
|  | 6b14bce25e | ||
|  | 5aa346327f | ||
|  | fcf8516f0d | ||
|  | 2a340d4d83 | ||
|  | 508d7a37e3 | ||
|  | 08fece39d8 | ||
|  | 857a0b1006 | ||
|  | 239d59c864 | ||
|  | 7297053dd6 | ||
|  | bd872f624a | ||
|  | 86acb14f05 | ||
|  | 9825f349ac | ||
|  | 19fd562c24 | ||
|  | b6d939a9ff | ||
|  | 1619edb8a1 | ||
|  | 782821aeb2 | ||
|  | 51498958fa | ||
|  | 51edb74474 | ||
|  | d5ab2a7557 | ||
|  | ee3525cfb2 | ||
|  | e9bd0858e2 | ||
|  | f46d881866 | ||
|  | ee5e718f83 | ||
|  | e25083aea4 | ||
|  | e74f0ca6f8 | ||
|  | 52b0fa9a54 | ||
|  | 105c007f03 | ||
|  | 0a3d5fd5d4 | ||
|  | ae3c02d4b2 | ||
|  | 9e17d07a17 | ||
|  | dcfab4e011 | ||
|  | 0b78f9361d | ||
|  | 0c4e9dc813 | ||
|  | d894cae073 | ||
|  | 84f553a911 | ||
|  | 05580deaa9 | ||
|  | aac83bbb91 | ||
|  | 249f1f48a6 | ||
|  | bfb92fe667 | ||
|  | 640dc43bbe | ||
|  | 8859d915b0 | ||
|  | 82fdb37c6b | ||
|  | 49c18dab63 | ||
|  | f5b8a22bde | ||
|  | b6b0f9c47d | ||
|  | a2a2fa2311 | ||
|  | 34690e3e65 | ||
|  | 54f58257f9 | ||
|  | 58b6c4668f | ||
|  | d0e7eb8409 | ||
|  | 77a7a8f30e | ||
|  | f76738e02b | ||
|  | ab147df2f1 | ||
|  | 5444ccc857 | ||
|  | fc89d62f1a | ||
|  | 94467273c5 | ||
|  | 835de64bea | ||
|  | 113da3437b | ||
|  | b0b6d01357 | ||
|  | 6cc24dc763 | ||
|  | 0cb3105cea | ||
|  | 00502a4689 | ||
|  | ebb7242b71 | ||
|  | 0fbb96c4ac | ||
|  | e942ffed71 | ||
|  | 319270bf2b | ||
|  | 8b69de0d54 | ||
|  | f929513310 | ||
|  | 0d25b82087 | ||
|  | c75857b1ea | ||
|  | 6d353869ef | ||
|  | 6ba7249a75 | ||
|  | 2ee543e2a4 | ||
|  | 871dfa9a67 | ||
|  | 17fe2d5863 | ||
|  | d5b62a2126 | ||
|  | a4a833f15f | ||
|  | a4fe077a23 | ||
|  | bfa77361b7 | ||
|  | 3dff529920 | ||
|  | 3681150010 | ||
|  | f0b4fcbdf0 | ||
|  | 5c7e8c5381 | ||
|  | a144fa449b | ||
|  | 4f9ceb0a80 | ||
|  | 12338fc0b4 | ||
|  | 6d2cc6e87d | ||
|  | 52598e5c5c | ||
|  | 903d810edc | ||
|  | 475b169952 | ||
|  | ba1f66367b | ||
|  | b27f560b33 | ||
|  | 59edca8fca | ||
|  | 147a90302d | ||
|  | b4b5aedc82 | ||
|  | 81293c9368 | ||
|  | 7c82b4c370 | ||
|  | 045e09f029 | ||
|  | cbd59f84fd | ||
|  | 086b206139 | ||
|  | d56e8d79ec | ||
|  | d61459e912 | ||
|  | 33880f3e86 | ||
|  | 6186881615 | ||
|  | 20a3396128 | ||
|  | e5ee98a6aa | ||
|  | 8985f1dba6 | ||
|  | 5e09884e99 | ||
|  | 0cb0a355bb | ||
|  | 26445851cc | ||
|  | 810691b5df | ||
|  | 5d18d02a9c | ||
|  | 65421639f1 | ||
|  | a474e9e5d1 | ||
|  | 7b9358a4f0 | ||
|  | ff684cad44 | ||
|  | 0b4f6dd29b | ||
|  | b4f512f667 | ||
|  | 7dda9974f1 | ||
|  | 95791254a9 | ||
|  | e0253a4ea8 | ||
|  | cfc2dba275 | ||
|  | 867e7bf665 | ||
|  | 7c448aed9f | ||
|  | 8a388a0d8b | ||
|  | 62a2e9de37 | ||
|  | 1184274246 | ||
|  | 51d66199b1 | ||
|  | e7e988f97a | ||
|  | a17211dc96 | ||
|  | 2c1769575c | ||
|  | 5d3af1a932 | ||
|  | f2e0ffb2b2 | ||
|  | 2e32b748b6 | ||
|  | 512b5204bc | ||
|  | 44bef2d6d9 | ||
|  | 7e2257b224 | ||
|  | 0b04ac79d0 | ||
|  | 7b02ad3c00 | ||
|  | 9b507f06c4 | ||
|  | 6ce92e1669 | ||
|  | d443304829 | ||
|  | c84f24fe20 | ||
|  | 9175b7a4c1 | ||
|  | 1c011fbd7b | ||
|  | 56531afd1a | ||
|  | b6e294c137 | ||
|  | 0f9936a542 | ||
|  | 9855f60f1f | ||
|  | 07adf67dfc | ||
|  | 125b03adfc | ||
|  | 528b65ae04 | ||
|  | 55fda94b6a | ||
|  | 67ab7a04ad | ||
|  | 94797a2d2b | ||
|  | 1bd40a0cff | ||
|  | 102003a52b | ||
|  | 6777ae046e | ||
|  | a32f55a920 | ||
|  | f3cc05e651 | ||
|  | 52a53791c8 | ||
|  | e181e340e4 | ||
|  | 2cf2cc46f2 | ||
|  | 11bfde3dc1 | ||
|  | dd6d4c67ed | ||
|  | f13b9d2764 | ||
|  | 50ed9b327f | ||
|  | 49f101308f | ||
|  | 2154033879 | ||
|  | ba1a540add | ||
|  | ea23e604f9 | ||
|  | 7cf9c15984 | ||
|  | a3f43f8ea3 | ||
|  | 3a933e6f2b | ||
|  | 9a15298e63 | ||
|  | 7367457500 | ||
|  | 831aec3af9 | ||
|  | 5b67b182c3 | ||
|  | 5fc0a298a5 | ||
|  | 69e3afc770 | ||
|  | 45d431f24e | ||
|  | 6a3a27026d | ||
|  | 0310637b07 | ||
|  | 9662d8a2ab | ||
|  | e81d8527c4 | ||
|  | 02a8bfb5ae | ||
|  | 845e95bd07 | ||
|  | 5eea8fda26 | ||
|  | b9133484f0 | ||
|  | ef4beaceb0 | ||
|  | 68dff22b77 | ||
|  | 0d0ae31d52 | ||
|  | bdcd889df4 | ||
|  | ef57259386 | ||
|  | 416ca05be4 | ||
|  | 479b17a8c2 | ||
|  | 624fd87751 | ||
|  | 727df8dd94 | ||
|  | 8ae9fc10eb | ||
|  | 1ff6502ea9 | ||
|  | 594165da6a | ||
|  | c03c72c7d0 | ||
|  | 78115e0504 | ||
|  | cb9ed5add0 | ||
|  | 4bb416945b | ||
|  | 1375a19a46 | ||
|  | 125b50b33e | ||
|  | 65d25c6f64 | ||
|  | 3879fa5df2 | ||
|  | df1cd7e07f | ||
|  | 2a9897057e | ||
|  | ea5d657e31 | ||
|  | a36a07ae6f | ||
|  | acaa220a70 | ||
|  | 3ffd1e3b41 | ||
|  | 14cedccaf3 | ||
|  | 041d8399ba | ||
|  | 32171508e1 | ||
|  | 11c15c47d1 | ||
|  | 853fc6678b | ||
|  | d6db49e621 | ||
|  | 405a98c8f3 | ||
|  | cb296ef2cd | ||
|  | bebd9e7a54 | ||
|  | ae0bd608a9 | ||
|  | 31d445c6a1 | ||
|  | f7f2c1730d | ||
|  | 2782c369c9 | ||
|  | 659db611ea | ||
|  | 9e5f7be1c5 | ||
|  | bd4f7ff3aa | ||
|  | 155aa3ba5c | ||
|  | 31cd1258f8 | ||
|  | 18e3b424d6 | ||
|  | d403688a0c | ||
|  | 17bf0c0623 | ||
|  | b296486892 | ||
|  | 64dccb192c | ||
|  | c15a27709d | ||
|  | 33de9f9337 | ||
|  | aa0ebd45b9 | ||
|  | 2583632883 | ||
|  | 2472d531f5 | ||
|  | fb43fb9af7 | ||
|  | 56a640a5c6 | ||
|  | ffa702935c | ||
|  | 8482203c3b | ||
|  | eb06eb7266 | ||
|  | ea64f505af | ||
|  | 748411c8db | ||
|  | 90aad8ed9a | ||
|  | 6cc2da03e0 | ||
|  | e1b94d6a28 | ||
|  | 1abd2e7d7e | ||
|  | 8352f18b3a | ||
|  | 9ff0b09d4d | ||
|  | 2e08825da8 | ||
|  | cf81f641bd | ||
|  | 07891edd16 | ||
|  | 6ea777d584 | ||
|  | 040ecb2532 | ||
|  | f5f8fa2471 | ||
|  | c728116788 | ||
|  | 6438f80526 | ||
|  | 0b43cf4b1c | ||
|  | 8677f78a56 | ||
|  | 54606b05c0 | ||
|  | 6ad126cf64 | ||
|  | 654b84f4f2 | ||
|  | 9ddf0c6e76 | ||
|  | 7e3984e341 | ||
|  | af77657a86 | ||
|  | eb03afef41 | ||
|  | 1da98c8a82 | ||
|  | a73c65eae9 | ||
|  | 5dc14666cc | ||
|  | dd219ad295 | ||
|  | 4e871e02e6 | ||
|  | cbc1030043 | ||
|  | 061c6e822d | ||
|  | 17d259dc52 | ||
|  | 0a319b31c8 | ||
|  | 8b1e34d800 | ||
|  | e548a05cf0 | ||
|  | 089b089fce | ||
|  | aa3e8c9986 | ||
|  | 497176d9d1 | ||
|  | 4e52369542 | ||
|  | ba37178ebb | ||
|  | 343de424ab | ||
|  | 2d790f3c4d | ||
|  | 651b2e140b | ||
|  | c859fa4c38 | ||
|  | 26e149a357 | ||
|  | 2ed765d204 | ||
|  | 8e091d712f | ||
|  | 812d9eb0e8 | ||
|  | ed43774129 | ||
|  | 945202652a | ||
|  | 6954fdec0a | ||
|  | 148ad7a3d1 | ||
|  | b209030f45 | ||
|  | 425dbd5604 | ||
|  | 4cf14ef5ac | ||
|  | f429065ab4 | ||
|  | 9d35575051 | ||
|  | b5ad057f06 | ||
|  | ab2af1669c | ||
|  | 11615685b7 | ||
|  | b017cb7b54 | ||
|  | 960bea96b4 | ||
|  | 979cfc9af0 | ||
|  | ed8202891f | ||
|  | 6f5a368c86 | ||
|  | c6c615308c | ||
|  | 105dfd96c1 | ||
|  | 65c51124f8 | ||
|  | 6309af9db8 | ||
|  | 5360ec6ec3 | ||
|  | 7c7cfc8c39 | ||
|  | f3207649ff | ||
|  | 6086db3b0b | ||
|  | fd4c91301e | ||
|  | 581d7144d6 | ||
|  | a458c7056d | ||
|  | 7b8822f664 | ||
|  | c8cd4e2d01 | ||
|  | abe6b05c5c | ||
|  | 1b6b74c67b | ||
|  | df5cbaaea8 | ||
|  | 9a14e90642 | ||
|  | fb337f57b8 | ||
|  | 9f09083247 | ||
|  | 74e55720d7 | ||
|  | e30e4fe447 | ||
|  | 5d38f4bb77 | ||
|  | dbef39a7a6 | ||
|  | f66b196046 | ||
|  | bb59cb204d | ||
|  | 731bcc7e93 | ||
|  | b3dbb19afc | ||
|  | b4e1e32d20 | ||
|  | c9cceb9e2d | ||
|  | fe13fd899c | ||
|  | 81b2999dc4 | ||
|  | 598ece677d | ||
|  | 2142fc8876 | ||
|  | 67d96a63f1 | ||
|  | efc46c17b2 | ||
|  | acae5616f8 | ||
|  | 672320f38b | ||
|  | f3cd3bb63f | ||
|  | ea98435acd | ||
|  | d545f8ddda | ||
|  | d4685e5f95 | ||
|  | e7907a4664 | ||
|  | 60862ffc3e | ||
|  | 154ee06d70 | ||
|  | 22d87c7c9d | ||
|  | e8f8b41f8b | ||
|  | 079f90766f | ||
|  | acc7f3dfe5 | ||
|  | 234d8989d7 | ||
|  | acb6aeffd1 | ||
|  | b8135557e5 | ||
|  | d54772815b | ||
|  | 5f7f8a31e9 | ||
|  | 56798131b3 | ||
|  | 3b932cfa7b | ||
|  | 2d27100b5d | ||
|  | 5e1bd0d679 | ||
|  | d954cf01f3 | ||
|  | e655da45e2 | ||
|  | c7246b3a84 | ||
|  | bef6bb7004 | ||
|  | ef8c567ab6 | ||
|  | 0fd001f229 | ||
|  | 55ce0e34c9 | ||
|  | 70bf1a48d0 | ||
|  | 7b4e8324a9 | ||
|  | 3913bf68c6 | ||
|  | ea48115190 | ||
|  | 9fdd4d0fba | ||
|  | bec97982a6 | ||
|  | 3a4990e3d4 | ||
|  | 995ddf6d98 | ||
|  | 1997a607ba | ||
|  | 3d406c2d07 | ||
|  | 591331b70c | ||
|  | 4273f2db34 | ||
|  | cb913a9adc | ||
|  | 7c5898ddf6 | ||
|  | a07e5d7833 | ||
|  | 9766f61cf8 | ||
|  | 58657e53bf | ||
|  | 391c92a6c6 | ||
|  | 65480ebe96 | ||
|  | 5e5fe434e2 | ||
|  | b628c9b027 | ||
|  | 928e418f3f | ||
|  | e5089dc126 | ||
|  | 6cff0375fb | ||
|  | afa8a1f298 | ||
|  | 8c3462079b | ||
|  | f1eeb08d90 | ||
|  | cad250f02a | ||
|  | 13e60a6048 | ||
|  | e115f5f2f4 | ||
|  | 348bd00fa3 | ||
|  | 2417517aee | ||
|  | 05880981f8 | ||
|  | d4f8b93fe3 | ||
|  | 7f64fa7037 | ||
|  | eca2be1a2e | ||
|  | 3d03a5b319 | ||
|  | 59385fc08d | ||
|  | fcabba1087 | ||
|  | 2ac24d62a0 | ||
|  | f0b9c21169 | ||
|  | 46e46e60a6 | ||
|  | 606290e185 | ||
|  | 845b57e931 | ||
|  | e7ca94bab1 | ||
|  | a7bb4ee50c | ||
|  | 339f85741f | ||
|  | 63374ee876 | ||
|  | e77ede91b9 | ||
|  | 30d7651322 | ||
|  | f101d70523 | ||
|  | 5cf84980e8 | ||
|  | 3035eb4a54 | ||
|  | b4710edc18 | ||
|  | 3e2a6492f4 | ||
|  | e11b1bb2ec | ||
|  | 57318cc55d | ||
|  | 8d3d93e537 | ||
|  | df5edb67f0 | ||
|  | 12b40f2bbe | ||
|  | 8660db3bb3 | ||
|  | 649099a841 | ||
|  | b4f91a9bbd | ||
|  | f72ce39c13 | ||
|  | f5420f1f07 | ||
|  | 2cf7c45ac5 | ||
|  | 4ceb79afa3 | ||
|  | 84671e5972 | ||
|  | 9c84530593 | ||
|  | 363af040d6 | ||
|  | 2ea30af4c4 | ||
|  | 6ccef6c1d0 | ||
|  | 2a9624d404 | ||
|  | a1f404d548 | ||
|  | c95a197028 | ||
|  | 82c62091aa | 
| @@ -1,49 +0,0 @@ | ||||
| ### 使用版本(未按照模板填写直接删除) | ||||
|  | ||||
| - jdk版本(带上尾号): 例如 1.8.0_202 | ||||
| - 框架版本(项目启动时输出的版本号): 例如 4.4.0 | ||||
| - 其他依赖版本(你觉得有必要的): | ||||
|  | ||||
| ### 问题前提 | ||||
|  | ||||
| > 功能不好用 不会用 是否已经看过项目文档 | ||||
| > 项目运行报错 是否已经拿着报错信息去百度 常见报错百度百度足以 | ||||
| > 是否搜索过其他issue 一些已经解决的问题 会在issue内留下解决方法 | ||||
| > 无法线上解决或者与框架无关的问题的欢迎加VIP群跟作者一对一谈 | ||||
|  | ||||
| ### 异常模块 | ||||
|  | ||||
| > 此报错都涉及到那些系统模块 | ||||
|  | ||||
| 例如 ruoyi-system ruoyi-auth 等等 | ||||
|  | ||||
| ### 问题描述 | ||||
|  | ||||
| > 越详细越容易直击问题所在 | ||||
|  | ||||
| 已知: XXX功能不好用 或 XXX数据不正常 等等 | ||||
|  | ||||
| ### 希望结果 | ||||
|  | ||||
| > 想知道你觉得怎么样是正常或者合理的 | ||||
|  | ||||
| 希望功能可以有XXX结果 或者 XXX现象 | ||||
|  | ||||
| ### 重现步骤 | ||||
|  | ||||
| > 作者并不知道这个问题是如何出现的 | ||||
|  | ||||
| - 1 | ||||
| - 2 | ||||
| - 3 | ||||
|  | ||||
| ### 相关代码与报错信息(请勿发混乱格式) | ||||
|  | ||||
| > 代码可按照如下形式提供或者截图均可 越详细越好 | ||||
| > 大多数问题都是 代码编写错误问题 逻辑问题 或者用法错误等问题 | ||||
|  | ||||
| ```java | ||||
| public class XXX { | ||||
|  | ||||
| } | ||||
| ``` | ||||
| @@ -9,8 +9,9 @@ body: | ||||
|       label: 版本 | ||||
|       description: 你当前正在使用我们软件的哪个版本(pom文件内的版本号)? | ||||
|       value: | | ||||
|         jdk版本(带上尾号): 例如 17.0.8 | ||||
|         框架版本(项目启动时输出的版本号): 例如 5.1.1 | ||||
|         注意: 未填写版本号不予处理直接关闭或删除 | ||||
|         jdk版本(带上尾号): | ||||
|         框架版本(项目启动时输出的版本号): | ||||
|         其他依赖版本(你觉得有必要的): | ||||
|     validations: | ||||
|       required: true | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> | ||||
|     <deployment type="dockerfile"> | ||||
|       <settings> | ||||
|         <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.1.2" /> | ||||
|         <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.3.1" /> | ||||
|         <option name="buildOnly" value="true" /> | ||||
|         <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" /> | ||||
|       </settings> | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> | ||||
|     <deployment type="dockerfile"> | ||||
|       <settings> | ||||
|         <option name="imageTag" value="ruoyi/ruoyi-server:5.1.2" /> | ||||
|         <option name="imageTag" value="ruoyi/ruoyi-server:5.3.1" /> | ||||
|         <option name="buildOnly" value="true" /> | ||||
|         <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" /> | ||||
|       </settings> | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| <component name="ProjectRunConfigurationManager"> | ||||
|   <configuration default="false" name="ruoyi-powerjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> | ||||
|   <configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> | ||||
|     <deployment type="dockerfile"> | ||||
|       <settings> | ||||
|         <option name="imageTag" value="ruoyi/ruoyi-powerjob-server:5.1.2" /> | ||||
|         <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.3.1" /> | ||||
|         <option name="buildOnly" value="true" /> | ||||
|         <option name="sourceFilePath" value="ruoyi-extend/ruoyi-powerjob-server/Dockerfile" /> | ||||
|         <option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" /> | ||||
|       </settings> | ||||
|     </deployment> | ||||
|     <method v="2" /> | ||||
							
								
								
									
										52
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								README.md
									
									
									
									
									
								
							| @@ -6,24 +6,35 @@ | ||||
|  | ||||
| [](https://gitee.com/dromara/RuoYi-Vue-Plus) | ||||
| [](https://github.com/dromara/RuoYi-Vue-Plus) | ||||
| [](https://gitcode.com/dromara/RuoYi-Vue-Plus) | ||||
| [](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE) | ||||
| [](https://www.jetbrains.com/?from=RuoYi-Vue-Plus) | ||||
| <br> | ||||
| [](https://gitee.com/dromara/RuoYi-Vue-Plus) | ||||
| []() | ||||
| [](https://gitee.com/dromara/RuoYi-Vue-Plus) | ||||
| []() | ||||
| []() | ||||
| []() | ||||
|  | ||||
| > RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群与多租户` 场景全方位升级(不兼容原框架) | ||||
| > Dromara RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群与多租户` 场景全方位升级(不兼容原框架) | ||||
|  | ||||
| > 项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可<br> | ||||
| 活到老写到老 为兴趣而开源 为学习而开源 为让大家真正可以学到技术而开源 | ||||
|  | ||||
| > 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system) | ||||
|  | ||||
| > 前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui) | ||||
| > 官方前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)<br> | ||||
| > 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5) | ||||
|  | ||||
| > 文档地址: [plus-doc](https://plus-doc.dromara.org) - [plus-doc(国内备用)](https://dromara.gitee.io/plus-doc) | ||||
| > 文档地址: [plus-doc](https://plus-doc.dromara.org) | ||||
|  | ||||
| ## 赞助商 | ||||
|  | ||||
| MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br> | ||||
| CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br> | ||||
| 数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br> | ||||
| 引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br> | ||||
| <font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br> | ||||
| [如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group) | ||||
|  | ||||
| # 本框架与RuoYi的功能差异 | ||||
|  | ||||
| @@ -48,15 +59,16 @@ | ||||
| | 数据加解密       | 采用 注解 + mybatis 拦截器 对存取数据期间自动加解密<br/>支持多种策略 如BASE64、AES、RSA、SM2、SM4等                                              | 无                                                                                  | | ||||
| | 接口传输加密      | 采用 动态 AES + RSA 加密请求 body 每一次请求秘钥都不同大幅度降低可破解性                                                                     | 无                                                                                  | | ||||
| | 数据翻译        | 采用 注解 + jackson 序列化期间动态修改数据 数据进行翻译<br/>支持多种模式: `映射翻译` `直接翻译` `其他扩展条件翻译` 接口化两步即可完成自定义扩展 内置多种翻译实现                   | 无                                                                                  | | ||||
| | 多数据源框架      | 采用 dynamic-datasource 支持世面大部分数据库<br/>通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源<br/>支持spel表达式从请求头参数等条件切换数据源            | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差                                                     | | ||||
| | 多数据源框架      | 采用 dynamic-datasource 支持市面大部分数据库<br/>通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源<br/>支持spel表达式从请求头参数等条件切换数据源            | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差                                                     | | ||||
| | 多数据源事务      | 采用 dynamic-datasource 支持多数据源不同种类的数据库事务回滚                                                                          | 不支持                                                                                | | ||||
| | 数据库连接池      | 采用 HikariCP Spring官方内置连接池 配置简单 以性能与稳定性闻名天下                                                                        | 采用 druid bug众多 社区维护差 活跃度低 配置众多繁琐性能一般                                               | | ||||
| | 数据库主键       | 采用 雪花ID 基于时间戳的 有序增长 唯一ID 再也不用为分库分表 数据合并主键冲突重复而发愁                                                                  | 采用 数据库自增ID 支持数据量有限 不支持多数据源主键唯一                                                     | | ||||
| | WebSocket协议 | 基于 Spring 封装的 WebSocket 协议 扩展了Token鉴权与分布式会话同步 不再只是基于单机的废物                                                         | 无                                                                                  | | ||||
| | SSE推送       | 采用 Spring SSE 实现 扩展了Token鉴权与分布式会话同步                                                                               | 无                                                                                  | | ||||
| | 序列化         | 采用 Jackson Spring官方内置序列化 靠谱!!!                                                                                    | 采用 fastjson bugjson 远近闻名                                                           |  | ||||
| | 分布式幂等       | 参考美团GTIS防重系统简化实现(细节可看文档)                                                                                          | 手动编写注解基于aop实现                                                                      | | ||||
| | 分布式锁        | 采用 Lock4j 底层基于 Redisson                                                                                           | 无                                                                                  | | ||||
| | 分布式任务调度     | 采用 PowerJob 天生支持分布式 统一的管理中心                                                                                       | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造                                                   |  | ||||
| | 分布式任务调度     | 采用 SnailJob 天生支持分布式 统一的管理中心 支持多种数据库 支持分片重试DAG任务流等                                                                 | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造                                                   |  | ||||
| | 文件存储        | 采用 Minio 分布式文件存储 天生支持多机、多硬盘、多分片、多副本存储<br/>支持权限管理 安全可靠 文件可加密存储                                                     | 采用 本机文件存储 文件裸漏 易丢失泄漏 不支持集群有单点效应                                                    | | ||||
| | 云存储         | 采用 AWS S3 协议客户端 支持 七牛、阿里、腾讯 等一切支持S3协议的厂家                                                                          | 不支持                                                                                | | ||||
| | 短信          | 采用 sms4j 短信融合包 支持数十种短信厂家 只需在yml配置好厂家密钥即可使用 可多厂家共用                                                                 | 不支持                                                                                | | ||||
| @@ -64,6 +76,7 @@ | ||||
| | 接口文档        | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了                                                     | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成                                                |  | ||||
| | 校验框架        | 采用 Validation 支持注解与工具类校验 注解支持国际化                                                                                  | 仅支持注解 且注解不支持国际化                                                                    | | ||||
| | Excel框架     | 采用 Alibaba EasyExcel 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等                                               | 基于 POI 手写实现 功能有限 复杂 扩展性差                                                           | | ||||
| | 工作流支持       | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能                                                                                   | 无                                                                                  | | ||||
| | 工具类框架       | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码                                                       | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等                                            |  | ||||
| | 监控框架        | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控                                    | 无                                                                                  |  | ||||
| | 链路追踪        | 采用 Apache SkyWalking 还在为请求不知道去哪了 到哪出了问题而烦恼吗<br/>用了它即可实时查看请求经过的每一处每一个节点                                            | 无                                                                                  | | ||||
| @@ -115,13 +128,12 @@ | ||||
| > | ||||
| >[部署项目 必看](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy) | ||||
| >>[https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy) | ||||
| >  | ||||
| > | ||||
| >[如何加群](https://plus-doc.dromara.org/#/common/add_group) | ||||
| >>[https://plus-doc.dromara.org/#/common/add_group](https://plus-doc.dromara.org/#/common/add_group) | ||||
| > | ||||
| >[参考文档 Wiki](https://plus-doc.dromara.org) | ||||
| >>[https://plus-doc.dromara.org](https://plus-doc.dromara.org) | ||||
| >  | ||||
| >[参考文档(国内备份)](https://dromara.gitee.io/plus-doc) | ||||
| >>[https://dromara.gitee.io/plus-doc](https://dromara.gitee.io/plus-doc) | ||||
|  | ||||
|  | ||||
| ## 软件架构图 | ||||
|  | ||||
| @@ -131,18 +143,6 @@ | ||||
|  | ||||
| [参与贡献的方式 https://plus-doc.dromara.org/#/common/contribution](https://plus-doc.dromara.org/#/common/contribution) | ||||
|  | ||||
| ### 其他 | ||||
|  | ||||
| * 定期同步升级 RuoYi-Vue 有用的更新 | ||||
| * GitHub 地址 [RuoYi-Vue-Plus](https://github.com/dromara/RuoYi-Vue-Plus) | ||||
| * 微服务 分支 [RuoYi-Cloud-Plus](https://gitee.com/JavaLionLi/RuoYi-Cloud-Plus) | ||||
| * 前端项目 地址 [plus-ui](https://gitee.com/JavaLionLi/plus-ui) | ||||
| * 用户扩展项目 [扩展项目列表](https://plus-doc.dromara.org/#/ruoyi-vue-plus/extend-project/list) | ||||
|  | ||||
| ## 加群与捐献 | ||||
| >[加群与捐献](https://plus-doc.dromara.org/#/ruoyi-vue-plus/other/group_chat) | ||||
| >>[https://plus-doc.dromara.org/#/ruoyi-vue-plus/other/group_chat](https://plus-doc.dromara.org/#/ruoyi-vue-plus/other/group_chat) | ||||
|  | ||||
| ## 捐献作者 | ||||
| 作者为兼职做开源,平时还需要工作,如果帮到了您可以请作者吃个盒饭   | ||||
| <img src="https://foruda.gitee.com/images/1678975784848381069/d8661ed9_1766278.png" width="300px" height="450px" /> | ||||
| @@ -168,8 +168,8 @@ | ||||
| |  |  | | ||||
| |  |  | | ||||
| |  |  | | ||||
| |  |  | | ||||
| |  |  | | ||||
| |  |  | | ||||
| |  |  | | ||||
| |  |  | | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										154
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										154
									
								
								pom.xml
									
									
									
									
									
								
							| @@ -10,52 +10,55 @@ | ||||
|  | ||||
|     <name>RuoYi-Vue-Plus</name> | ||||
|     <url>https://gitee.com/dromara/RuoYi-Vue-Plus</url> | ||||
|     <description>RuoYi-Vue-Plus多租户管理系统</description> | ||||
|     <description>Dromara RuoYi-Vue-Plus多租户管理系统</description> | ||||
|  | ||||
|     <properties> | ||||
|         <revision>5.1.2</revision> | ||||
|         <spring-boot.version>3.1.7</spring-boot.version> | ||||
|         <revision>5.3.1</revision> | ||||
|         <spring-boot.version>3.4.4</spring-boot.version> | ||||
|         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||||
|         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | ||||
|         <java.version>17</java.version> | ||||
|         <spring-boot.mybatis>3.0.3</spring-boot.mybatis> | ||||
|         <springdoc.version>2.2.0</springdoc.version> | ||||
|         <mybatis.version>3.5.16</mybatis.version> | ||||
|         <springdoc.version>2.8.5</springdoc.version> | ||||
|         <therapi-javadoc.version>0.15.0</therapi-javadoc.version> | ||||
|         <poi.version>5.2.3</poi.version> | ||||
|         <easyexcel.version>3.3.3</easyexcel.version> | ||||
|         <easyexcel.version>4.0.3</easyexcel.version> | ||||
|         <velocity.version>2.3</velocity.version> | ||||
|         <satoken.version>1.37.0</satoken.version> | ||||
|         <mybatis-plus.version>3.5.4</mybatis-plus.version> | ||||
|         <satoken.version>1.40.0</satoken.version> | ||||
|         <mybatis-plus.version>3.5.11</mybatis-plus.version> | ||||
|         <p6spy.version>3.9.1</p6spy.version> | ||||
|         <hutool.version>5.8.22</hutool.version> | ||||
|         <okhttp.version>4.10.0</okhttp.version> | ||||
|         <spring-boot-admin.version>3.1.8</spring-boot-admin.version> | ||||
|         <redisson.version>3.24.3</redisson.version> | ||||
|         <lock4j.version>2.2.5</lock4j.version> | ||||
|         <dynamic-ds.version>4.2.0</dynamic-ds.version> | ||||
|         <alibaba-ttl.version>2.14.4</alibaba-ttl.version> | ||||
|         <powerjob.version>4.3.6</powerjob.version> | ||||
|         <mapstruct-plus.version>1.3.5</mapstruct-plus.version> | ||||
|         <hutool.version>5.8.35</hutool.version> | ||||
|         <spring-boot-admin.version>3.4.5</spring-boot-admin.version> | ||||
|         <redisson.version>3.45.1</redisson.version> | ||||
|         <lock4j.version>2.2.7</lock4j.version> | ||||
|         <dynamic-ds.version>4.3.1</dynamic-ds.version> | ||||
|         <snailjob.version>1.4.0</snailjob.version> | ||||
|         <mapstruct-plus.version>1.4.6</mapstruct-plus.version> | ||||
|         <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version> | ||||
|         <lombok.version>1.18.30</lombok.version> | ||||
|         <lombok.version>1.18.36</lombok.version> | ||||
|         <bouncycastle.version>1.76</bouncycastle.version> | ||||
|         <justauth.version>1.16.6</justauth.version> | ||||
|         <justauth.version>1.16.7</justauth.version> | ||||
|         <!-- 离线IP地址定位库 --> | ||||
|         <ip2region.version>2.7.0</ip2region.version> | ||||
|  | ||||
|         <!-- OSS 配置 --> | ||||
|         <aws-java-sdk-s3.version>1.12.600</aws-java-sdk-s3.version> | ||||
|         <aws.sdk.version>2.28.22</aws.sdk.version> | ||||
|         <!-- SMS 配置 --> | ||||
|         <sms4j.version>2.2.0</sms4j.version> | ||||
|         <sms4j.version>3.3.4</sms4j.version> | ||||
|         <!-- 限制框架中的fastjson版本 --> | ||||
|         <fastjson.version>1.2.83</fastjson.version> | ||||
|         <!-- 面向运行时的D-ORM依赖 --> | ||||
|         <anyline.version>8.7.2-20250101</anyline.version> | ||||
|         <!--工作流配置--> | ||||
|         <warm-flow.version>1.6.8</warm-flow.version> | ||||
|  | ||||
|         <!-- 插件版本 --> | ||||
|         <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version> | ||||
|         <maven-war-plugin.version>3.2.2</maven-war-plugin.version> | ||||
|         <maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison> | ||||
|         <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version> | ||||
|         <maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version> | ||||
|         <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version> | ||||
|         <!-- 打包默认跳过测试 --> | ||||
|         <skipTests>true</skipTests> | ||||
|     </properties> | ||||
|  | ||||
|     <profiles> | ||||
| @@ -65,6 +68,8 @@ | ||||
|                 <!-- 环境标识,需要与配置文件的名称相对应 --> | ||||
|                 <profiles.active>local</profiles.active> | ||||
|                 <logging.level>info</logging.level> | ||||
|                 <monitor.username>ruoyi</monitor.username> | ||||
|                 <monitor.password>123456</monitor.password> | ||||
|             </properties> | ||||
|         </profile> | ||||
|         <profile> | ||||
| @@ -73,6 +78,8 @@ | ||||
|                 <!-- 环境标识,需要与配置文件的名称相对应 --> | ||||
|                 <profiles.active>dev</profiles.active> | ||||
|                 <logging.level>info</logging.level> | ||||
|                 <monitor.username>ruoyi</monitor.username> | ||||
|                 <monitor.password>123456</monitor.password> | ||||
|             </properties> | ||||
|             <activation> | ||||
|                 <!-- 默认环境 --> | ||||
| @@ -84,6 +91,8 @@ | ||||
|             <properties> | ||||
|                 <profiles.active>prod</profiles.active> | ||||
|                 <logging.level>warn</logging.level> | ||||
|                 <monitor.username>ruoyi</monitor.username> | ||||
|                 <monitor.password>123456</monitor.password> | ||||
|             </properties> | ||||
|         </profile> | ||||
|     </profiles> | ||||
| @@ -110,6 +119,18 @@ | ||||
|                 <scope>import</scope> | ||||
|             </dependency> | ||||
|  | ||||
|             <!-- Warm-Flow国产工作流引擎, 在线文档:http://warm-flow.cn/ --> | ||||
|             <dependency> | ||||
|                 <groupId>org.dromara.warm</groupId> | ||||
|                 <artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId> | ||||
|                 <version>${warm-flow.version}</version> | ||||
|             </dependency> | ||||
|             <dependency> | ||||
|                 <groupId>org.dromara.warm</groupId> | ||||
|                 <artifactId>warm-flow-plugin-ui-sb-web</artifactId> | ||||
|                 <version>${warm-flow.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <!-- JustAuth 的依赖配置--> | ||||
|             <dependency> | ||||
|                 <groupId>me.zhyd.oauth</groupId> | ||||
| @@ -144,26 +165,10 @@ | ||||
|                 <version>${lombok.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <dependency> | ||||
|                 <groupId>org.apache.poi</groupId> | ||||
|                 <artifactId>poi</artifactId> | ||||
|                 <version>${poi.version}</version> | ||||
|             </dependency> | ||||
|             <dependency> | ||||
|                 <groupId>org.apache.poi</groupId> | ||||
|                 <artifactId>poi-ooxml</artifactId> | ||||
|                 <version>${poi.version}</version> | ||||
|             </dependency> | ||||
|             <dependency> | ||||
|                 <groupId>com.alibaba</groupId> | ||||
|                 <artifactId>easyexcel</artifactId> | ||||
|                 <version>${easyexcel.version}</version> | ||||
|                 <exclusions> | ||||
|                     <exclusion> | ||||
|                         <groupId>org.apache.poi</groupId> | ||||
|                         <artifactId>poi-ooxml-schemas</artifactId> | ||||
|                     </exclusion> | ||||
|                 </exclusions> | ||||
|             </dependency> | ||||
|  | ||||
|             <!-- velocity代码生成使用模板 --> | ||||
| @@ -205,14 +210,20 @@ | ||||
|             </dependency> | ||||
|  | ||||
|             <dependency> | ||||
|                 <groupId>org.mybatis.spring.boot</groupId> | ||||
|                 <artifactId>mybatis-spring-boot-starter</artifactId> | ||||
|                 <version>${spring-boot.mybatis}</version> | ||||
|                 <groupId>org.mybatis</groupId> | ||||
|                 <artifactId>mybatis</artifactId> | ||||
|                 <version>${mybatis.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <dependency> | ||||
|                 <groupId>com.baomidou</groupId> | ||||
|                 <artifactId>mybatis-plus-boot-starter</artifactId> | ||||
|                 <artifactId>mybatis-plus-spring-boot3-starter</artifactId> | ||||
|                 <version>${mybatis-plus.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <dependency> | ||||
|                 <groupId>com.baomidou</groupId> | ||||
|                 <artifactId>mybatis-plus-jsqlparser</artifactId> | ||||
|                 <version>${mybatis-plus.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
| @@ -229,16 +240,23 @@ | ||||
|                 <version>${p6spy.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <!--  AWS SDK for Java 2.x  --> | ||||
|             <dependency> | ||||
|                 <groupId>com.squareup.okhttp3</groupId> | ||||
|                 <artifactId>okhttp</artifactId> | ||||
|                 <version>${okhttp.version}</version> | ||||
|                 <groupId>software.amazon.awssdk</groupId> | ||||
|                 <artifactId>s3</artifactId> | ||||
|                 <version>${aws.sdk.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 --> | ||||
|             <dependency> | ||||
|                 <groupId>com.amazonaws</groupId> | ||||
|                 <artifactId>aws-java-sdk-s3</artifactId> | ||||
|                 <version>${aws-java-sdk-s3.version}</version> | ||||
|                 <groupId>software.amazon.awssdk</groupId> | ||||
|                 <artifactId>s3-transfer-manager</artifactId> | ||||
|                 <version>${aws.sdk.version}</version> | ||||
|             </dependency> | ||||
|             <!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 --> | ||||
|             <dependency> | ||||
|                 <groupId>software.amazon.awssdk</groupId> | ||||
|                 <artifactId>netty-nio-client</artifactId> | ||||
|                 <version>${aws.sdk.version}</version> | ||||
|             </dependency> | ||||
|             <!--短信sms4j--> | ||||
|             <dependency> | ||||
| @@ -271,22 +289,16 @@ | ||||
|                 <version>${lock4j.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <!-- PowerJob --> | ||||
|             <!-- SnailJob Client --> | ||||
|             <dependency> | ||||
|                 <groupId>tech.powerjob</groupId> | ||||
|                 <artifactId>powerjob-worker-spring-boot-starter</artifactId> | ||||
|                 <version>${powerjob.version}</version> | ||||
|                 <groupId>com.aizuda</groupId> | ||||
|                 <artifactId>snail-job-client-starter</artifactId> | ||||
|                 <version>${snailjob.version}</version> | ||||
|             </dependency> | ||||
|             <dependency> | ||||
|                 <groupId>tech.powerjob</groupId> | ||||
|                 <artifactId>powerjob-official-processors</artifactId> | ||||
|                 <version>${powerjob.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <dependency> | ||||
|                 <groupId>com.alibaba</groupId> | ||||
|                 <artifactId>transmittable-thread-local</artifactId> | ||||
|                 <version>${alibaba-ttl.version}</version> | ||||
|                 <groupId>com.aizuda</groupId> | ||||
|                 <artifactId>snail-job-client-job-core</artifactId> | ||||
|                 <version>${snailjob.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <!-- 加密包引入 --> | ||||
| @@ -309,6 +321,12 @@ | ||||
|                 <version>${ip2region.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <dependency> | ||||
|                 <groupId>commons-io</groupId> | ||||
|                 <artifactId>commons-io</artifactId> | ||||
|                 <version>2.15.0</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <dependency> | ||||
|                 <groupId>com.alibaba</groupId> | ||||
|                 <artifactId>fastjson</artifactId> | ||||
| @@ -339,6 +357,13 @@ | ||||
|                 <version>${revision}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <!--  工作流模块  --> | ||||
|             <dependency> | ||||
|                 <groupId>org.dromara</groupId> | ||||
|                 <artifactId>ruoyi-workflow</artifactId> | ||||
|                 <version>${revision}</version> | ||||
|             </dependency> | ||||
|  | ||||
|         </dependencies> | ||||
|     </dependencyManagement> | ||||
|  | ||||
| @@ -355,7 +380,7 @@ | ||||
|             <plugin> | ||||
|                 <groupId>org.apache.maven.plugins</groupId> | ||||
|                 <artifactId>maven-compiler-plugin</artifactId> | ||||
|                 <version>${maven-compiler-plugin.verison}</version> | ||||
|                 <version>${maven-compiler-plugin.version}</version> | ||||
|                 <configuration> | ||||
|                     <source>${java.version}</source> | ||||
|                     <target>${java.version}</target> | ||||
| @@ -398,6 +423,7 @@ | ||||
|                 <artifactId>maven-surefire-plugin</artifactId> | ||||
|                 <version>${maven-surefire-plugin.version}</version> | ||||
|                 <configuration> | ||||
|                     <argLine>-Dfile.encoding=UTF-8</argLine> | ||||
|                     <!-- 根据打包环境执行对应的@Tag测试方法 --> | ||||
|                     <groups>${profiles.active}</groups> | ||||
|                     <!-- 排除标签 --> | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| # 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/ | ||||
| FROM bellsoft/liberica-openjdk-debian:17.0.11-cds | ||||
| #FROM bellsoft/liberica-openjdk-debian:21.0.5-cds | ||||
| #FROM findepi/graalvm:java17-native | ||||
| FROM openjdk:17.0.2-oraclelinux8 | ||||
|  | ||||
| MAINTAINER Lion Li | ||||
| LABEL maintainer="Lion Li" | ||||
|  | ||||
| RUN mkdir -p /ruoyi/server/logs \ | ||||
|     /ruoyi/server/temp \ | ||||
| @@ -14,11 +16,15 @@ ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="" | ||||
| EXPOSE ${SERVER_PORT} | ||||
|  | ||||
| ADD ./target/ruoyi-admin.jar ./app.jar | ||||
| # 工作流字体文件 | ||||
| ADD ./zhFonts/ /usr/share/fonts/zhFonts/ | ||||
|  | ||||
| SHELL ["/bin/bash", "-c"] | ||||
|  | ||||
| ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \ | ||||
|            # 应用名称 如果想区分集群节点监控 改成不同的名称即可 | ||||
|            #-Dskywalking.agent.service_name=ruoyi-server \ | ||||
|            #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \ | ||||
|            -jar app.jar \ | ||||
|            -XX:+HeapDumpOnOutOfMemoryError -Xlog:gc*,:time,tags,level -XX:+UseZGC ${JAVA_OPTS} | ||||
|            -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \ | ||||
|            -jar app.jar | ||||
|  | ||||
|   | ||||
| @@ -22,21 +22,28 @@ | ||||
|             <groupId>com.mysql</groupId> | ||||
|             <artifactId>mysql-connector-j</artifactId> | ||||
|         </dependency> | ||||
|         <!-- Oracle --> | ||||
|         <dependency> | ||||
|             <groupId>com.oracle.database.jdbc</groupId> | ||||
|             <artifactId>ojdbc8</artifactId> | ||||
|         </dependency> | ||||
|         <!-- PostgreSql --> | ||||
|         <dependency> | ||||
|             <groupId>org.postgresql</groupId> | ||||
|             <artifactId>postgresql</artifactId> | ||||
|         </dependency> | ||||
|         <!-- SqlServer --> | ||||
|         <dependency> | ||||
|             <groupId>com.microsoft.sqlserver</groupId> | ||||
|             <artifactId>mssql-jdbc</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
| <!--        <!– mp支持的数据库均支持 只需要增加对应的jdbc依赖即可 –>--> | ||||
| <!--        <!– Oracle –>--> | ||||
| <!--        <dependency>--> | ||||
| <!--            <groupId>com.oracle.database.jdbc</groupId>--> | ||||
| <!--            <artifactId>ojdbc8</artifactId>--> | ||||
| <!--        </dependency>--> | ||||
| <!--        <!– 兼容oracle低版本 –>--> | ||||
| <!--        <dependency>--> | ||||
| <!--            <groupId>com.oracle.database.nls</groupId>--> | ||||
| <!--            <artifactId>orai18n</artifactId>--> | ||||
| <!--        </dependency>--> | ||||
| <!--        <!– PostgreSql –>--> | ||||
| <!--        <dependency>--> | ||||
| <!--            <groupId>org.postgresql</groupId>--> | ||||
| <!--            <artifactId>postgresql</artifactId>--> | ||||
| <!--        </dependency>--> | ||||
| <!--        <!– SqlServer –>--> | ||||
| <!--        <dependency>--> | ||||
| <!--            <groupId>com.microsoft.sqlserver</groupId>--> | ||||
| <!--            <artifactId>mssql-jdbc</artifactId>--> | ||||
| <!--        </dependency>--> | ||||
|  | ||||
|         <dependency> | ||||
|             <groupId>org.dromara</groupId> | ||||
| @@ -53,6 +60,11 @@ | ||||
|             <artifactId>ruoyi-common-ratelimiter</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <dependency> | ||||
|             <groupId>org.dromara</groupId> | ||||
|             <artifactId>ruoyi-common-mail</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <dependency> | ||||
|             <groupId>org.dromara</groupId> | ||||
|             <artifactId>ruoyi-system</artifactId> | ||||
| @@ -75,6 +87,12 @@ | ||||
|             <artifactId>ruoyi-demo</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!--  工作流模块  --> | ||||
|         <dependency> | ||||
|             <groupId>org.dromara</groupId> | ||||
|             <artifactId>ruoyi-workflow</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <dependency> | ||||
|             <groupId>de.codecentric</groupId> | ||||
|             <artifactId>spring-boot-admin-starter-client</artifactId> | ||||
| @@ -86,11 +104,6 @@ | ||||
|             <scope>test</scope> | ||||
|         </dependency> | ||||
|  | ||||
|         <dependency> | ||||
|             <groupId>me.zhyd.oauth</groupId> | ||||
|             <artifactId>JustAuth</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- skywalking 整合 logback --> | ||||
| <!--        <dependency>--> | ||||
| <!--            <groupId>org.apache.skywalking</groupId>--> | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| package org.dromara.web.controller; | ||||
|  | ||||
| import cn.dev33.satoken.annotation.SaIgnore; | ||||
| import cn.dev33.satoken.exception.NotLoginException; | ||||
| 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 jakarta.servlet.http.HttpServletRequest; | ||||
| @@ -10,7 +13,7 @@ import me.zhyd.oauth.model.AuthResponse; | ||||
| import me.zhyd.oauth.model.AuthUser; | ||||
| import me.zhyd.oauth.request.AuthRequest; | ||||
| import me.zhyd.oauth.utils.AuthStateUtils; | ||||
| import org.dromara.common.core.constant.UserConstants; | ||||
| import org.dromara.common.core.constant.SystemConstants; | ||||
| import org.dromara.common.core.domain.R; | ||||
| import org.dromara.common.core.domain.model.LoginBody; | ||||
| import org.dromara.common.core.domain.model.RegisterBody; | ||||
| @@ -22,10 +25,11 @@ import org.dromara.common.satoken.utils.LoginHelper; | ||||
| import org.dromara.common.social.config.properties.SocialLoginConfigProperties; | ||||
| import org.dromara.common.social.config.properties.SocialProperties; | ||||
| 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.common.websocket.utils.WebSocketUtils; | ||||
| import org.dromara.system.domain.SysClient; | ||||
| import org.dromara.system.domain.bo.SysTenantBo; | ||||
| import org.dromara.system.domain.vo.SysClientVo; | ||||
| import org.dromara.system.domain.vo.SysTenantVo; | ||||
| import org.dromara.system.service.ISysClientService; | ||||
| import org.dromara.system.service.ISysConfigService; | ||||
| @@ -41,7 +45,10 @@ import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| import java.net.URL; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.ScheduledExecutorService; | ||||
| import java.util.concurrent.TimeUnit; | ||||
|  | ||||
| @@ -81,12 +88,12 @@ public class AuthController { | ||||
|         // 授权类型和客户端id | ||||
|         String clientId = loginBody.getClientId(); | ||||
|         String grantType = loginBody.getGrantType(); | ||||
|         SysClient client = clientService.queryByClientId(clientId); | ||||
|         SysClientVo client = clientService.queryByClientId(clientId); | ||||
|         // 查询不到 client 或 client 内不包含 grantType | ||||
|         if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) { | ||||
|             log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType); | ||||
|             return R.fail(MessageUtils.message("auth.grant.type.error")); | ||||
|         } else if (!UserConstants.NORMAL.equals(client.getStatus())) { | ||||
|         } else if (!SystemConstants.NORMAL.equals(client.getStatus())) { | ||||
|             return R.fail(MessageUtils.message("auth.grant.type.blocked")); | ||||
|         } | ||||
|         // 校验租户 | ||||
| @@ -96,36 +103,46 @@ public class AuthController { | ||||
|  | ||||
|         Long userId = LoginHelper.getUserId(); | ||||
|         scheduledExecutorService.schedule(() -> { | ||||
|             WebSocketUtils.sendMessage(userId, "欢迎登录RuoYi-Vue-Plus后台管理系统"); | ||||
|         }, 3, TimeUnit.SECONDS); | ||||
|             SseMessageDto dto = new SseMessageDto(); | ||||
|             dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统"); | ||||
|             dto.setUserIds(List.of(userId)); | ||||
|             SseMessageUtils.publishMessage(dto); | ||||
|         }, 5, TimeUnit.SECONDS); | ||||
|         return R.ok(loginVo); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 第三方登录请求 | ||||
|      * 获取跳转URL | ||||
|      * | ||||
|      * @param source 登录来源 | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     @GetMapping("/binding/{source}") | ||||
|     public R<String> authBinding(@PathVariable("source") String source) { | ||||
|     public R<String> authBinding(@PathVariable("source") String source, | ||||
|                                  @RequestParam String tenantId, @RequestParam String domain) { | ||||
|         SocialLoginConfigProperties obj = socialProperties.getType().get(source); | ||||
|         if (ObjectUtil.isNull(obj)) { | ||||
|             return R.fail(source + "平台账号暂不支持"); | ||||
|         } | ||||
|         AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties); | ||||
|         String authorizeUrl = authRequest.authorize(AuthStateUtils.createState()); | ||||
|         Map<String, String> map = new HashMap<>(); | ||||
|         map.put("tenantId", tenantId); | ||||
|         map.put("domain", domain); | ||||
|         map.put("state", AuthStateUtils.createState()); | ||||
|         String authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8)); | ||||
|         return R.ok("操作成功", authorizeUrl); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 第三方登录回调业务处理 绑定授权 | ||||
|      * 前端回调绑定授权(需要token) | ||||
|      * | ||||
|      * @param loginBody 请求体 | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     @PostMapping("/social/callback") | ||||
|     public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) { | ||||
|         // 校验token | ||||
|         StpUtil.checkLogin(); | ||||
|         // 获取第三方登录信息 | ||||
|         AuthResponse<AuthUser> response = SocialUtils.loginAuth( | ||||
|                 loginBody.getSource(), loginBody.getSocialCode(), | ||||
| @@ -141,12 +158,14 @@ public class AuthController { | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 取消授权 | ||||
|      * 取消授权(需要token) | ||||
|      * | ||||
|      * @param socialId socialId | ||||
|      */ | ||||
|     @DeleteMapping(value = "/unlock/{socialId}") | ||||
|     public R<Void> unlockSocial(@PathVariable Long socialId) { | ||||
|         // 校验token | ||||
|         StpUtil.checkLogin(); | ||||
|         Boolean rows = socialUserService.deleteWithValidById(socialId); | ||||
|         return rows ? R.ok() : R.fail("取消授权失败"); | ||||
|     } | ||||
| @@ -181,8 +200,26 @@ public class AuthController { | ||||
|      */ | ||||
|     @GetMapping("/tenant/list") | ||||
|     public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception { | ||||
|         // 返回对象 | ||||
|         LoginTenantVo result = new LoginTenantVo(); | ||||
|         boolean enable = TenantHelper.isEnable(); | ||||
|         result.setTenantEnabled(enable); | ||||
|         // 如果未开启租户这直接返回 | ||||
|         if (!enable) { | ||||
|             return R.ok(result); | ||||
|         } | ||||
|  | ||||
|         List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo()); | ||||
|         List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class); | ||||
|         try { | ||||
|             // 如果只超管返回所有租户 | ||||
|             if (LoginHelper.isSuperAdmin()) { | ||||
|                 result.setVoList(voList); | ||||
|                 return R.ok(result); | ||||
|             } | ||||
|         } catch (NotLoginException ignored) { | ||||
|         } | ||||
|  | ||||
|         // 获取域名 | ||||
|         String host; | ||||
|         String referer = request.getHeader("referer"); | ||||
| @@ -194,12 +231,9 @@ public class AuthController { | ||||
|         } | ||||
|         // 根据域名进行筛选 | ||||
|         List<TenantListVo> list = StreamUtils.filter(voList, vo -> | ||||
|                 StringUtils.equals(vo.getDomain(), host)); | ||||
|         // 返回对象 | ||||
|         LoginTenantVo vo = new LoginTenantVo(); | ||||
|         vo.setVoList(CollUtil.isNotEmpty(list) ? list : voList); | ||||
|         vo.setTenantEnabled(TenantHelper.isEnable()); | ||||
|         return R.ok(vo); | ||||
|             StringUtils.equalsIgnoreCase(vo.getDomain(), host)); | ||||
|         result.setVoList(CollUtil.isNotEmpty(list) ? list : voList); | ||||
|         return R.ok(result); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -5,9 +5,13 @@ import cn.hutool.captcha.AbstractCaptcha; | ||||
| import cn.hutool.captcha.generator.CodeGenerator; | ||||
| import cn.hutool.core.util.IdUtil; | ||||
| import cn.hutool.core.util.RandomUtil; | ||||
| import jakarta.validation.constraints.NotBlank; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.dromara.common.core.constant.Constants; | ||||
| import org.dromara.common.core.constant.GlobalConstants; | ||||
| import org.dromara.common.core.domain.R; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
| import org.dromara.common.core.utils.SpringUtils; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import org.dromara.common.core.utils.reflect.ReflectUtils; | ||||
| @@ -21,11 +25,7 @@ import org.dromara.common.web.enums.CaptchaType; | ||||
| import org.dromara.sms4j.api.SmsBlend; | ||||
| import org.dromara.sms4j.api.entity.SmsResponse; | ||||
| import org.dromara.sms4j.core.factory.SmsFactory; | ||||
| import org.dromara.sms4j.provider.enumerate.SupplierType; | ||||
| import org.dromara.web.domain.vo.CaptchaVo; | ||||
| import jakarta.validation.constraints.NotBlank; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.expression.Expression; | ||||
| import org.springframework.expression.ExpressionParser; | ||||
| import org.springframework.expression.spel.standard.SpelExpressionParser; | ||||
| @@ -66,11 +66,11 @@ public class CaptchaController { | ||||
|         String templateId = ""; | ||||
|         LinkedHashMap<String, String> map = new LinkedHashMap<>(1); | ||||
|         map.put("code", code); | ||||
|         SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA); | ||||
|         SmsBlend smsBlend = SmsFactory.getSmsBlend("config1"); | ||||
|         SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map); | ||||
|         if (!"OK".equals(smsResponse.getCode())) { | ||||
|         if (!smsResponse.isSuccess()) { | ||||
|             log.error("验证码短信发送异常 => {}", smsResponse); | ||||
|             return R.fail(smsResponse.getMessage()); | ||||
|             return R.fail(smsResponse.getData().toString()); | ||||
|         } | ||||
|         return R.ok(); | ||||
|     } | ||||
| @@ -80,12 +80,21 @@ public class CaptchaController { | ||||
|      * | ||||
|      * @param email 邮箱 | ||||
|      */ | ||||
|     @RateLimiter(key = "#email", time = 60, count = 1) | ||||
|     @GetMapping("/resource/email/code") | ||||
|     public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) { | ||||
|         if (!mailProperties.getEnabled()) { | ||||
|             return R.fail("当前系统没有开启邮箱功能!"); | ||||
|         } | ||||
|         SpringUtils.getAopProxy(this).emailCodeImpl(email); | ||||
|         return R.ok(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 邮箱验证码 | ||||
|      * 独立方法避免验证码关闭之后仍然走限流 | ||||
|      */ | ||||
|     @RateLimiter(key = "#email", time = 60, count = 1) | ||||
|     public void emailCodeImpl(String email) { | ||||
|         String key = GlobalConstants.CAPTCHA_CODE_KEY + email; | ||||
|         String code = RandomUtil.randomNumbers(4); | ||||
|         RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); | ||||
| @@ -93,23 +102,30 @@ public class CaptchaController { | ||||
|             MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。"); | ||||
|         } catch (Exception e) { | ||||
|             log.error("验证码短信发送异常 => {}", e.getMessage()); | ||||
|             return R.fail(e.getMessage()); | ||||
|             throw new ServiceException(e.getMessage()); | ||||
|         } | ||||
|         return R.ok(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 生成验证码 | ||||
|      */ | ||||
|     @RateLimiter(time = 60, count = 10, limitType = LimitType.IP) | ||||
|     @GetMapping("/auth/code") | ||||
|     public R<CaptchaVo> getCode() { | ||||
|         CaptchaVo captchaVo = new CaptchaVo(); | ||||
|         boolean captchaEnabled = captchaProperties.getEnable(); | ||||
|         if (!captchaEnabled) { | ||||
|             CaptchaVo captchaVo = new CaptchaVo(); | ||||
|             captchaVo.setCaptchaEnabled(false); | ||||
|             return R.ok(captchaVo); | ||||
|         } | ||||
|         return R.ok(SpringUtils.getAopProxy(this).getCodeImpl()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 生成验证码 | ||||
|      * 独立方法避免验证码关闭之后仍然走限流 | ||||
|      */ | ||||
|     @RateLimiter(time = 60, count = 10, limitType = LimitType.IP) | ||||
|     public CaptchaVo getCodeImpl() { | ||||
|         // 保存验证码信息 | ||||
|         String uuid = IdUtil.simpleUUID(); | ||||
|         String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid; | ||||
| @@ -121,6 +137,7 @@ public class CaptchaController { | ||||
|         AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz()); | ||||
|         captcha.setGenerator(codeGenerator); | ||||
|         captcha.createCode(); | ||||
|         // 如果是数学验证码,使用SpEL表达式处理验证码结果 | ||||
|         String code = captcha.getCode(); | ||||
|         if (isMath) { | ||||
|             ExpressionParser parser = new SpelExpressionParser(); | ||||
| @@ -128,9 +145,10 @@ public class CaptchaController { | ||||
|             code = exp.getValue(String.class); | ||||
|         } | ||||
|         RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); | ||||
|         CaptchaVo captchaVo = new CaptchaVo(); | ||||
|         captchaVo.setUuid(uuid); | ||||
|         captchaVo.setImg(captcha.getImageBase64()); | ||||
|         return R.ok(captchaVo); | ||||
|         return captchaVo; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| package org.dromara.web.controller; | ||||
|  | ||||
| import cn.dev33.satoken.annotation.SaIgnore; | ||||
| import org.dromara.common.core.config.RuoYiConfig; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import org.dromara.common.core.utils.SpringUtils; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
|  | ||||
| @@ -17,16 +17,12 @@ import org.springframework.web.bind.annotation.RestController; | ||||
| @RestController | ||||
| public class IndexController { | ||||
|  | ||||
|     /** | ||||
|      * 系统基础配置 | ||||
|      */ | ||||
|     private final RuoYiConfig ruoyiConfig; | ||||
|  | ||||
|     /** | ||||
|      * 访问首页,提示语 | ||||
|      */ | ||||
|     @GetMapping("/") | ||||
|     public String index() { | ||||
|         return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion()); | ||||
|         return StringUtils.format("欢迎使用{}后台管理框架,请通过前端地址访问。", SpringUtils.getApplicationName()); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -13,10 +13,19 @@ import lombok.Data; | ||||
| @AutoMapper(target = SysTenantVo.class) | ||||
| public class TenantListVo { | ||||
|  | ||||
|     /** | ||||
|      * 租户编号 | ||||
|      */ | ||||
|     private String tenantId; | ||||
|  | ||||
|     /** | ||||
|      * 企业名称 | ||||
|      */ | ||||
|     private String companyName; | ||||
|  | ||||
|     /** | ||||
|      * 域名 | ||||
|      */ | ||||
|     private String domain; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -3,6 +3,8 @@ package org.dromara.web.listener; | ||||
| import cn.dev33.satoken.config.SaTokenConfig; | ||||
| import cn.dev33.satoken.listener.SaTokenListener; | ||||
| import cn.dev33.satoken.stp.SaLoginModel; | ||||
| import cn.dev33.satoken.stp.StpUtil; | ||||
| import cn.hutool.core.convert.Convert; | ||||
| import cn.hutool.http.useragent.UserAgent; | ||||
| import cn.hutool.http.useragent.UserAgentUtil; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| @@ -10,7 +12,6 @@ import lombok.extern.slf4j.Slf4j; | ||||
| import org.dromara.common.core.constant.CacheConstants; | ||||
| import org.dromara.common.core.constant.Constants; | ||||
| import org.dromara.common.core.domain.dto.UserOnlineDTO; | ||||
| import org.dromara.common.core.domain.model.LoginUser; | ||||
| import org.dromara.common.core.utils.MessageUtils; | ||||
| import org.dromara.common.core.utils.ServletUtils; | ||||
| import org.dromara.common.core.utils.SpringUtils; | ||||
| @@ -18,6 +19,7 @@ import org.dromara.common.core.utils.ip.AddressUtils; | ||||
| import org.dromara.common.log.event.LogininforEvent; | ||||
| import org.dromara.common.redis.utils.RedisUtils; | ||||
| import org.dromara.common.satoken.utils.LoginHelper; | ||||
| import org.dromara.common.tenant.helper.TenantHelper; | ||||
| import org.dromara.web.service.SysLoginService; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| @@ -43,7 +45,6 @@ public class UserActionListener implements SaTokenListener { | ||||
|     public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) { | ||||
|         UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent")); | ||||
|         String ip = ServletUtils.getClientIP(); | ||||
|         LoginUser user = LoginHelper.getLoginUser(); | ||||
|         UserOnlineDTO dto = new UserOnlineDTO(); | ||||
|         dto.setIpaddr(ip); | ||||
|         dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); | ||||
| @@ -51,25 +52,29 @@ public class UserActionListener implements SaTokenListener { | ||||
|         dto.setOs(userAgent.getOs().getName()); | ||||
|         dto.setLoginTime(System.currentTimeMillis()); | ||||
|         dto.setTokenId(tokenValue); | ||||
|         dto.setUserName(user.getUsername()); | ||||
|         dto.setClientKey(user.getClientKey()); | ||||
|         dto.setDeviceType(user.getDeviceType()); | ||||
|         dto.setDeptName(user.getDeptName()); | ||||
|         if(tokenConfig.getTimeout() == -1) { | ||||
|             RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto); | ||||
|         } else { | ||||
|             RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout())); | ||||
|         } | ||||
|         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)); | ||||
|         dto.setDeviceType(loginModel.getDevice()); | ||||
|         dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY)); | ||||
|         TenantHelper.dynamic(tenantId, () -> { | ||||
|             if(tokenConfig.getTimeout() == -1) { | ||||
|                 RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto); | ||||
|             } else { | ||||
|                 RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout())); | ||||
|             } | ||||
|         }); | ||||
|         // 记录登录日志 | ||||
|         LogininforEvent logininforEvent = new LogininforEvent(); | ||||
|         logininforEvent.setTenantId(user.getTenantId()); | ||||
|         logininforEvent.setUsername(user.getUsername()); | ||||
|         logininforEvent.setTenantId(tenantId); | ||||
|         logininforEvent.setUsername(username); | ||||
|         logininforEvent.setStatus(Constants.LOGIN_SUCCESS); | ||||
|         logininforEvent.setMessage(MessageUtils.message("user.login.success")); | ||||
|         logininforEvent.setRequest(ServletUtils.getRequest()); | ||||
|         SpringUtils.context().publishEvent(logininforEvent); | ||||
|         // 更新登录信息 | ||||
|         loginService.recordLoginInfo(user.getUserId(), ip); | ||||
|         loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip); | ||||
|         log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue); | ||||
|     } | ||||
|  | ||||
| @@ -78,7 +83,10 @@ public class UserActionListener implements SaTokenListener { | ||||
|      */ | ||||
|     @Override | ||||
|     public void doLogout(String loginType, Object loginId, String tokenValue) { | ||||
|         RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue); | ||||
|         String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY)); | ||||
|         TenantHelper.dynamic(tenantId, () -> { | ||||
|             RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue); | ||||
|         }); | ||||
|         log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue); | ||||
|     } | ||||
|  | ||||
| @@ -87,7 +95,10 @@ public class UserActionListener implements SaTokenListener { | ||||
|      */ | ||||
|     @Override | ||||
|     public void doKickout(String loginType, Object loginId, String tokenValue) { | ||||
|         RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue); | ||||
|         String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY)); | ||||
|         TenantHelper.dynamic(tenantId, () -> { | ||||
|             RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue); | ||||
|         }); | ||||
|         log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue); | ||||
|     } | ||||
|  | ||||
| @@ -96,7 +107,10 @@ public class UserActionListener implements SaTokenListener { | ||||
|      */ | ||||
|     @Override | ||||
|     public void doReplaced(String loginType, Object loginId, String tokenValue) { | ||||
|         RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue); | ||||
|         String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY)); | ||||
|         TenantHelper.dynamic(tenantId, () -> { | ||||
|             RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue); | ||||
|         }); | ||||
|         log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ package org.dromara.web.service; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
| import org.dromara.common.core.utils.SpringUtils; | ||||
| import org.dromara.system.domain.SysClient; | ||||
| import org.dromara.system.domain.vo.SysClientVo; | ||||
| import org.dromara.web.domain.vo.LoginVo; | ||||
|  | ||||
| /** | ||||
| @@ -17,8 +18,13 @@ public interface IAuthStrategy { | ||||
|  | ||||
|     /** | ||||
|      * 登录 | ||||
|      * | ||||
|      * @param body      登录对象 | ||||
|      * @param client    授权管理视图对象 | ||||
|      * @param grantType 授权类型 | ||||
|      * @return 登录验证信息 | ||||
|      */ | ||||
|     static LoginVo login(String body, SysClient client, String grantType) { | ||||
|     static LoginVo login(String body, SysClientVo client, String grantType) { | ||||
|         // 授权类型和客户端id | ||||
|         String beanName = grantType + BASE_NAME; | ||||
|         if (!SpringUtils.containsBean(beanName)) { | ||||
| @@ -30,7 +36,11 @@ public interface IAuthStrategy { | ||||
|  | ||||
|     /** | ||||
|      * 登录 | ||||
|      * | ||||
|      * @param body   登录对象 | ||||
|      * @param client 授权管理视图对象 | ||||
|      * @return 登录验证信息 | ||||
|      */ | ||||
|     LoginVo login(String body, SysClient client); | ||||
|     LoginVo login(String body, SysClientVo client); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -4,17 +4,21 @@ import cn.dev33.satoken.exception.NotLoginException; | ||||
| import cn.dev33.satoken.stp.StpUtil; | ||||
| import cn.hutool.core.bean.BeanUtil; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.lang.Opt; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import com.baomidou.lock.annotation.Lock4j; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import me.zhyd.oauth.model.AuthUser; | ||||
| import org.dromara.common.core.constant.CacheConstants; | ||||
| import org.dromara.common.core.constant.Constants; | ||||
| import org.dromara.common.core.constant.GlobalConstants; | ||||
| import org.dromara.common.core.constant.SystemConstants; | ||||
| import org.dromara.common.core.constant.TenantConstants; | ||||
| import org.dromara.common.core.domain.dto.PostDTO; | ||||
| import org.dromara.common.core.domain.dto.RoleDTO; | ||||
| import org.dromara.common.core.domain.model.LoginUser; | ||||
| import org.dromara.common.core.enums.LoginType; | ||||
| import org.dromara.common.core.enums.TenantStatus; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
| import org.dromara.common.core.exception.user.UserException; | ||||
| import org.dromara.common.core.utils.*; | ||||
| import org.dromara.common.log.event.LogininforEvent; | ||||
| @@ -25,13 +29,9 @@ import org.dromara.common.tenant.exception.TenantException; | ||||
| import org.dromara.common.tenant.helper.TenantHelper; | ||||
| import org.dromara.system.domain.SysUser; | ||||
| import org.dromara.system.domain.bo.SysSocialBo; | ||||
| import org.dromara.system.domain.vo.SysSocialVo; | ||||
| import org.dromara.system.domain.vo.SysTenantVo; | ||||
| import org.dromara.system.domain.vo.SysUserVo; | ||||
| import org.dromara.system.domain.vo.*; | ||||
| import org.dromara.system.mapper.SysUserMapper; | ||||
| import org.dromara.system.service.ISysPermissionService; | ||||
| import org.dromara.system.service.ISysSocialService; | ||||
| import org.dromara.system.service.ISysTenantService; | ||||
| import org.dromara.system.service.*; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| @@ -59,6 +59,9 @@ public class SysLoginService { | ||||
|     private final ISysTenantService tenantService; | ||||
|     private final ISysPermissionService permissionService; | ||||
|     private final ISysSocialService sysSocialService; | ||||
|     private final ISysRoleService roleService; | ||||
|     private final ISysDeptService deptService; | ||||
|     private final ISysPostService postService; | ||||
|     private final SysUserMapper userMapper; | ||||
|  | ||||
|  | ||||
| @@ -66,20 +69,28 @@ public class SysLoginService { | ||||
|      * 绑定第三方用户 | ||||
|      * | ||||
|      * @param authUserData 授权响应实体 | ||||
|      * @return 统一响应实体 | ||||
|      */ | ||||
|     @Lock4j | ||||
|     public void socialRegister(AuthUser authUserData) { | ||||
|         String authId = authUserData.getSource() + authUserData.getUuid(); | ||||
|         // 第三方用户信息 | ||||
|         SysSocialBo bo = BeanUtil.toBean(authUserData, SysSocialBo.class); | ||||
|         BeanUtil.copyProperties(authUserData.getToken(), bo); | ||||
|         bo.setUserId(LoginHelper.getUserId()); | ||||
|         Long userId = LoginHelper.getUserId(); | ||||
|         bo.setUserId(userId); | ||||
|         bo.setAuthId(authId); | ||||
|         bo.setOpenId(authUserData.getUuid()); | ||||
|         bo.setUserName(authUserData.getUsername()); | ||||
|         bo.setNickName(authUserData.getNickname()); | ||||
|         List<SysSocialVo> checkList = sysSocialService.selectByAuthId(authId); | ||||
|         if (CollUtil.isNotEmpty(checkList)) { | ||||
|             throw new ServiceException("此三方账号已经被绑定!"); | ||||
|         } | ||||
|         // 查询是否已经绑定用户 | ||||
|         List<SysSocialVo> list = sysSocialService.selectByAuthId(authId); | ||||
|         SysSocialBo params = new SysSocialBo(); | ||||
|         params.setUserId(userId); | ||||
|         params.setSource(bo.getSource()); | ||||
|         List<SysSocialVo> list = sysSocialService.queryList(params); | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             // 没有绑定用户, 新增用户信息 | ||||
|             sysSocialService.insertByBo(bo); | ||||
| @@ -87,6 +98,8 @@ public class SysLoginService { | ||||
|             // 更新用户信息 | ||||
|             bo.setId(list.get(0).getId()); | ||||
|             sysSocialService.updateByBo(bo); | ||||
|             // 如果要绑定的平台账号已经被绑定过了 是否抛异常自行决断 | ||||
|             // throw new ServiceException("此平台账号已经被绑定!"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -132,23 +145,29 @@ public class SysLoginService { | ||||
|         SpringUtils.context().publishEvent(logininforEvent); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 构建登录用户 | ||||
|      */ | ||||
|     public LoginUser buildLoginUser(SysUserVo user) { | ||||
|         LoginUser loginUser = new LoginUser(); | ||||
|         Long userId = user.getUserId(); | ||||
|         loginUser.setTenantId(user.getTenantId()); | ||||
|         loginUser.setUserId(user.getUserId()); | ||||
|         loginUser.setUserId(userId); | ||||
|         loginUser.setDeptId(user.getDeptId()); | ||||
|         loginUser.setUsername(user.getUserName()); | ||||
|         loginUser.setNickname(user.getNickName()); | ||||
|         loginUser.setUserType(user.getUserType()); | ||||
|         loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId())); | ||||
|         loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId())); | ||||
|         loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName()); | ||||
|         List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class); | ||||
|         loginUser.setRoles(roles); | ||||
|         loginUser.setMenuPermission(permissionService.getMenuPermission(userId)); | ||||
|         loginUser.setRolePermission(permissionService.getRolePermission(userId)); | ||||
|         if (ObjectUtil.isNotNull(user.getDeptId())) { | ||||
|             Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById); | ||||
|             loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY)); | ||||
|             loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY)); | ||||
|         } | ||||
|         List<SysRoleVo> roles = roleService.selectRolesByUserId(userId); | ||||
|         List<SysPostVo> posts = postService.selectPostsByUserId(userId); | ||||
|         loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class)); | ||||
|         loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class)); | ||||
|         return loginUser; | ||||
|     } | ||||
|  | ||||
| @@ -170,7 +189,7 @@ public class SysLoginService { | ||||
|      * 登录校验 | ||||
|      */ | ||||
|     public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) { | ||||
|         String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username; | ||||
|         String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username; | ||||
|         String loginFail = Constants.LOGIN_FAIL; | ||||
|  | ||||
|         // 获取用户登录错误次数,默认为0 (可自定义限制策略 例如: key + username + ip) | ||||
| @@ -209,17 +228,17 @@ public class SysLoginService { | ||||
|         if (!TenantHelper.isEnable()) { | ||||
|             return; | ||||
|         } | ||||
|         if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) { | ||||
|             return; | ||||
|         } | ||||
|         if (StringUtils.isBlank(tenantId)) { | ||||
|             throw new TenantException("tenant.number.not.blank"); | ||||
|         } | ||||
|         if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) { | ||||
|             return; | ||||
|         } | ||||
|         SysTenantVo tenant = tenantService.queryByTenantId(tenantId); | ||||
|         if (ObjectUtil.isNull(tenant)) { | ||||
|             log.info("登录租户:{} 不存在.", tenantId); | ||||
|             throw new TenantException("tenant.not.exists"); | ||||
|         } else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) { | ||||
|         } else if (SystemConstants.DISABLE.equals(tenant.getStatus())) { | ||||
|             log.info("登录租户:{} 已被停用.", tenantId); | ||||
|             throw new TenantException("tenant.blocked"); | ||||
|         } else if (ObjectUtil.isNotNull(tenant.getExpireTime()) | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| package org.dromara.web.service; | ||||
|  | ||||
| import cn.dev33.satoken.secure.BCrypt; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import org.dromara.common.core.constant.Constants; | ||||
| @@ -61,8 +60,7 @@ public class SysRegisterService { | ||||
|  | ||||
|         boolean exist = TenantHelper.dynamic(tenantId, () -> { | ||||
|             return userMapper.exists(new LambdaQueryWrapper<SysUser>() | ||||
|                 .eq(SysUser::getUserName, sysUser.getUserName()) | ||||
|                 .ne(ObjectUtil.isNotNull(sysUser.getUserId()), SysUser::getUserId, sysUser.getUserId())); | ||||
|                 .eq(SysUser::getUserName, sysUser.getUserName())); | ||||
|         }); | ||||
|         if (exist) { | ||||
|             throw new UserException("user.register.save.error", username); | ||||
| @@ -82,15 +80,15 @@ public class SysRegisterService { | ||||
|      * @param uuid     唯一标识 | ||||
|      */ | ||||
|     public void validateCaptcha(String tenantId, String username, String code, String uuid) { | ||||
|         String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, ""); | ||||
|         String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, ""); | ||||
|         String captcha = RedisUtils.getCacheObject(verifyKey); | ||||
|         RedisUtils.deleteObject(verifyKey); | ||||
|         if (captcha == null) { | ||||
|             recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire")); | ||||
|             recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); | ||||
|             throw new CaptchaExpireException(); | ||||
|         } | ||||
|         if (!code.equalsIgnoreCase(captcha)) { | ||||
|             recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error")); | ||||
|             recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")); | ||||
|             throw new CaptchaException(); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -8,10 +8,10 @@ import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.dromara.common.core.constant.Constants; | ||||
| import org.dromara.common.core.constant.GlobalConstants; | ||||
| import org.dromara.common.core.constant.SystemConstants; | ||||
| import org.dromara.common.core.domain.model.EmailLoginBody; | ||||
| import org.dromara.common.core.domain.model.LoginUser; | ||||
| import org.dromara.common.core.enums.LoginType; | ||||
| import org.dromara.common.core.enums.UserStatus; | ||||
| import org.dromara.common.core.exception.user.CaptchaExpireException; | ||||
| import org.dromara.common.core.exception.user.UserException; | ||||
| import org.dromara.common.core.utils.MessageUtils; | ||||
| @@ -21,8 +21,8 @@ import org.dromara.common.json.utils.JsonUtils; | ||||
| import org.dromara.common.redis.utils.RedisUtils; | ||||
| import org.dromara.common.satoken.utils.LoginHelper; | ||||
| import org.dromara.common.tenant.helper.TenantHelper; | ||||
| import org.dromara.system.domain.SysClient; | ||||
| import org.dromara.system.domain.SysUser; | ||||
| import org.dromara.system.domain.vo.SysClientVo; | ||||
| import org.dromara.system.domain.vo.SysUserVo; | ||||
| import org.dromara.system.mapper.SysUserMapper; | ||||
| import org.dromara.web.domain.vo.LoginVo; | ||||
| @@ -44,19 +44,18 @@ public class EmailAuthStrategy implements IAuthStrategy { | ||||
|     private final SysUserMapper userMapper; | ||||
|  | ||||
|     @Override | ||||
|     public LoginVo login(String body, SysClient client) { | ||||
|     public LoginVo login(String body, SysClientVo client) { | ||||
|         EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class); | ||||
|         ValidatorUtils.validate(loginBody); | ||||
|         String tenantId = loginBody.getTenantId(); | ||||
|         String email = loginBody.getEmail(); | ||||
|         String emailCode = loginBody.getEmailCode(); | ||||
|  | ||||
|         // 通过邮箱查找用户 | ||||
|         SysUserVo user = loadUserByEmail(tenantId, email); | ||||
|  | ||||
|         loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode)); | ||||
|         // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了 | ||||
|         LoginUser loginUser = loginService.buildLoginUser(user); | ||||
|         LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { | ||||
|             SysUserVo user = loadUserByEmail(email); | ||||
|             loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode)); | ||||
|             // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了 | ||||
|             return loginService.buildLoginUser(user); | ||||
|         }); | ||||
|         loginUser.setClientKey(client.getClientKey()); | ||||
|         loginUser.setDeviceType(client.getDeviceType()); | ||||
|         SaLoginModel model = new SaLoginModel(); | ||||
| @@ -88,20 +87,16 @@ public class EmailAuthStrategy implements IAuthStrategy { | ||||
|         return code.equals(emailCode); | ||||
|     } | ||||
|  | ||||
|     private SysUserVo loadUserByEmail(String tenantId, String email) { | ||||
|         return TenantHelper.dynamic(tenantId, () -> { | ||||
|             SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>() | ||||
|                 .select(SysUser::getEmail, SysUser::getStatus) | ||||
|                 .eq(SysUser::getEmail, email)); | ||||
|             if (ObjectUtil.isNull(user)) { | ||||
|                 log.info("登录用户:{} 不存在.", email); | ||||
|                 throw new UserException("user.not.exists", email); | ||||
|             } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { | ||||
|                 log.info("登录用户:{} 已被停用.", email); | ||||
|                 throw new UserException("user.blocked", email); | ||||
|             } | ||||
|             return userMapper.selectUserByEmail(email); | ||||
|         }); | ||||
|     private SysUserVo loadUserByEmail(String email) { | ||||
|         SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email)); | ||||
|         if (ObjectUtil.isNull(user)) { | ||||
|             log.info("登录用户:{} 不存在.", email); | ||||
|             throw new UserException("user.not.exists", email); | ||||
|         } else if (SystemConstants.DISABLE.equals(user.getStatus())) { | ||||
|             log.info("登录用户:{} 已被停用.", email); | ||||
|             throw new UserException("user.blocked", email); | ||||
|         } | ||||
|         return user; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -9,10 +9,10 @@ import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.dromara.common.core.constant.Constants; | ||||
| import org.dromara.common.core.constant.GlobalConstants; | ||||
| import org.dromara.common.core.constant.SystemConstants; | ||||
| import org.dromara.common.core.domain.model.LoginUser; | ||||
| import org.dromara.common.core.domain.model.PasswordLoginBody; | ||||
| import org.dromara.common.core.enums.LoginType; | ||||
| import org.dromara.common.core.enums.UserStatus; | ||||
| import org.dromara.common.core.exception.user.CaptchaException; | ||||
| import org.dromara.common.core.exception.user.CaptchaExpireException; | ||||
| import org.dromara.common.core.exception.user.UserException; | ||||
| @@ -24,8 +24,8 @@ import org.dromara.common.redis.utils.RedisUtils; | ||||
| import org.dromara.common.satoken.utils.LoginHelper; | ||||
| import org.dromara.common.tenant.helper.TenantHelper; | ||||
| import org.dromara.common.web.config.properties.CaptchaProperties; | ||||
| import org.dromara.system.domain.SysClient; | ||||
| import org.dromara.system.domain.SysUser; | ||||
| import org.dromara.system.domain.vo.SysClientVo; | ||||
| import org.dromara.system.domain.vo.SysUserVo; | ||||
| import org.dromara.system.mapper.SysUserMapper; | ||||
| import org.dromara.web.domain.vo.LoginVo; | ||||
| @@ -48,7 +48,7 @@ public class PasswordAuthStrategy implements IAuthStrategy { | ||||
|     private final SysUserMapper userMapper; | ||||
|  | ||||
|     @Override | ||||
|     public LoginVo login(String body, SysClient client) { | ||||
|     public LoginVo login(String body, SysClientVo client) { | ||||
|         PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class); | ||||
|         ValidatorUtils.validate(loginBody); | ||||
|         String tenantId = loginBody.getTenantId(); | ||||
| @@ -62,11 +62,12 @@ public class PasswordAuthStrategy implements IAuthStrategy { | ||||
|         if (captchaEnabled) { | ||||
|             validateCaptcha(tenantId, username, code, uuid); | ||||
|         } | ||||
|  | ||||
|         SysUserVo user = loadUserByUsername(tenantId, username); | ||||
|         loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword())); | ||||
|         // 此处可根据登录用户的数据不同 自行创建 loginUser | ||||
|         LoginUser loginUser = loginService.buildLoginUser(user); | ||||
|         LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { | ||||
|             SysUserVo user = loadUserByUsername(username); | ||||
|             loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword())); | ||||
|             // 此处可根据登录用户的数据不同 自行创建 loginUser | ||||
|             return loginService.buildLoginUser(user); | ||||
|         }); | ||||
|         loginUser.setClientKey(client.getClientKey()); | ||||
|         loginUser.setDeviceType(client.getDeviceType()); | ||||
|         SaLoginModel model = new SaLoginModel(); | ||||
| @@ -94,7 +95,7 @@ public class PasswordAuthStrategy implements IAuthStrategy { | ||||
|      * @param uuid     唯一标识 | ||||
|      */ | ||||
|     private void validateCaptcha(String tenantId, String username, String code, String uuid) { | ||||
|         String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, ""); | ||||
|         String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, ""); | ||||
|         String captcha = RedisUtils.getCacheObject(verifyKey); | ||||
|         RedisUtils.deleteObject(verifyKey); | ||||
|         if (captcha == null) { | ||||
| @@ -107,20 +108,16 @@ public class PasswordAuthStrategy implements IAuthStrategy { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private SysUserVo loadUserByUsername(String tenantId, String username) { | ||||
|         return TenantHelper.dynamic(tenantId, () -> { | ||||
|             SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>() | ||||
|                 .select(SysUser::getUserName, SysUser::getStatus) | ||||
|                 .eq(SysUser::getUserName, username)); | ||||
|             if (ObjectUtil.isNull(user)) { | ||||
|                 log.info("登录用户:{} 不存在.", username); | ||||
|                 throw new UserException("user.not.exists", username); | ||||
|             } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { | ||||
|                 log.info("登录用户:{} 已被停用.", username); | ||||
|                 throw new UserException("user.blocked", username); | ||||
|             } | ||||
|             return userMapper.selectUserByUserName(username); | ||||
|         }); | ||||
|     private SysUserVo loadUserByUsername(String username) { | ||||
|         SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username)); | ||||
|         if (ObjectUtil.isNull(user)) { | ||||
|             log.info("登录用户:{} 不存在.", username); | ||||
|             throw new UserException("user.not.exists", username); | ||||
|         } else if (SystemConstants.DISABLE.equals(user.getStatus())) { | ||||
|             log.info("登录用户:{} 已被停用.", username); | ||||
|             throw new UserException("user.blocked", username); | ||||
|         } | ||||
|         return user; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -8,10 +8,10 @@ import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.dromara.common.core.constant.Constants; | ||||
| import org.dromara.common.core.constant.GlobalConstants; | ||||
| import org.dromara.common.core.constant.SystemConstants; | ||||
| import org.dromara.common.core.domain.model.LoginUser; | ||||
| import org.dromara.common.core.domain.model.SmsLoginBody; | ||||
| import org.dromara.common.core.enums.LoginType; | ||||
| import org.dromara.common.core.enums.UserStatus; | ||||
| import org.dromara.common.core.exception.user.CaptchaExpireException; | ||||
| import org.dromara.common.core.exception.user.UserException; | ||||
| import org.dromara.common.core.utils.MessageUtils; | ||||
| @@ -21,8 +21,8 @@ import org.dromara.common.json.utils.JsonUtils; | ||||
| import org.dromara.common.redis.utils.RedisUtils; | ||||
| import org.dromara.common.satoken.utils.LoginHelper; | ||||
| import org.dromara.common.tenant.helper.TenantHelper; | ||||
| import org.dromara.system.domain.SysClient; | ||||
| import org.dromara.system.domain.SysUser; | ||||
| import org.dromara.system.domain.vo.SysClientVo; | ||||
| import org.dromara.system.domain.vo.SysUserVo; | ||||
| import org.dromara.system.mapper.SysUserMapper; | ||||
| import org.dromara.web.domain.vo.LoginVo; | ||||
| @@ -44,19 +44,18 @@ public class SmsAuthStrategy implements IAuthStrategy { | ||||
|     private final SysUserMapper userMapper; | ||||
|  | ||||
|     @Override | ||||
|     public LoginVo login(String body, SysClient client) { | ||||
|     public LoginVo login(String body, SysClientVo client) { | ||||
|         SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class); | ||||
|         ValidatorUtils.validate(loginBody); | ||||
|         String tenantId = loginBody.getTenantId(); | ||||
|         String phonenumber = loginBody.getPhonenumber(); | ||||
|         String smsCode = loginBody.getSmsCode(); | ||||
|  | ||||
|         // 通过手机号查找用户 | ||||
|         SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber); | ||||
|  | ||||
|         loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode)); | ||||
|         // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了 | ||||
|         LoginUser loginUser = loginService.buildLoginUser(user); | ||||
|         LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { | ||||
|             SysUserVo user = loadUserByPhonenumber(phonenumber); | ||||
|             loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode)); | ||||
|             // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了 | ||||
|             return loginService.buildLoginUser(user); | ||||
|         }); | ||||
|         loginUser.setClientKey(client.getClientKey()); | ||||
|         loginUser.setDeviceType(client.getDeviceType()); | ||||
|         SaLoginModel model = new SaLoginModel(); | ||||
| @@ -88,20 +87,16 @@ public class SmsAuthStrategy implements IAuthStrategy { | ||||
|         return code.equals(smsCode); | ||||
|     } | ||||
|  | ||||
|     private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) { | ||||
|         return TenantHelper.dynamic(tenantId, () -> { | ||||
|             SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>() | ||||
|                 .select(SysUser::getPhonenumber, SysUser::getStatus) | ||||
|                 .eq(SysUser::getPhonenumber, phonenumber)); | ||||
|             if (ObjectUtil.isNull(user)) { | ||||
|                 log.info("登录用户:{} 不存在.", phonenumber); | ||||
|                 throw new UserException("user.not.exists", phonenumber); | ||||
|             } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { | ||||
|                 log.info("登录用户:{} 已被停用.", phonenumber); | ||||
|                 throw new UserException("user.blocked", phonenumber); | ||||
|             } | ||||
|             return userMapper.selectUserByPhonenumber(phonenumber); | ||||
|         }); | ||||
|     private SysUserVo loadUserByPhonenumber(String phonenumber) { | ||||
|         SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber)); | ||||
|         if (ObjectUtil.isNull(user)) { | ||||
|             log.info("登录用户:{} 不存在.", phonenumber); | ||||
|             throw new UserException("user.not.exists", phonenumber); | ||||
|         } else if (SystemConstants.DISABLE.equals(user.getStatus())) { | ||||
|             log.info("登录用户:{} 已被停用.", phonenumber); | ||||
|             throw new UserException("user.blocked", phonenumber); | ||||
|         } | ||||
|         return user; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -7,24 +7,23 @@ import cn.hutool.core.map.MapUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import cn.hutool.http.HttpUtil; | ||||
| import cn.hutool.http.Method; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import me.zhyd.oauth.model.AuthResponse; | ||||
| import me.zhyd.oauth.model.AuthUser; | ||||
| import org.dromara.common.core.constant.SystemConstants; | ||||
| import org.dromara.common.core.domain.model.LoginUser; | ||||
| import org.dromara.common.core.domain.model.SocialLoginBody; | ||||
| import org.dromara.common.core.enums.UserStatus; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
| import org.dromara.common.core.exception.user.UserException; | ||||
| import org.dromara.common.core.utils.StreamUtils; | ||||
| import org.dromara.common.core.utils.ValidatorUtils; | ||||
| import org.dromara.common.json.utils.JsonUtils; | ||||
| import org.dromara.common.satoken.utils.LoginHelper; | ||||
| import org.dromara.common.social.config.properties.SocialProperties; | ||||
| import org.dromara.common.social.utils.SocialUtils; | ||||
| import org.dromara.common.tenant.helper.TenantHelper; | ||||
| import org.dromara.system.domain.SysClient; | ||||
| import org.dromara.system.domain.SysUser; | ||||
| import org.dromara.system.domain.vo.SysClientVo; | ||||
| import org.dromara.system.domain.vo.SysSocialVo; | ||||
| import org.dromara.system.domain.vo.SysUserVo; | ||||
| import org.dromara.system.mapper.SysUserMapper; | ||||
| @@ -59,7 +58,7 @@ public class SocialAuthStrategy implements IAuthStrategy { | ||||
|      * @param client   客户端信息 | ||||
|      */ | ||||
|     @Override | ||||
|     public LoginVo login(String body, SysClient client) { | ||||
|     public LoginVo login(String body, SysClientVo client) { | ||||
|         SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class); | ||||
|         ValidatorUtils.validate(loginBody); | ||||
|         AuthResponse<AuthUser> response = SocialUtils.loginAuth( | ||||
| @@ -83,16 +82,21 @@ public class SocialAuthStrategy implements IAuthStrategy { | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!"); | ||||
|         } | ||||
|         Optional<SysSocialVo> opt = list.stream().filter(x -> x.getTenantId().equals(loginBody.getTenantId())).findAny(); | ||||
|         if (opt.isEmpty()) { | ||||
|             throw new ServiceException("对不起,你没有权限登录当前租户!"); | ||||
|         SysSocialVo social; | ||||
|         if (TenantHelper.isEnable()) { | ||||
|             Optional<SysSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId())); | ||||
|             if (opt.isEmpty()) { | ||||
|                 throw new ServiceException("对不起,你没有权限登录当前租户!"); | ||||
|             } | ||||
|             social = opt.get(); | ||||
|         } else { | ||||
|             social = list.get(0); | ||||
|         } | ||||
|         SysSocialVo social = opt.get(); | ||||
|         // 查找用户 | ||||
|         SysUserVo user = loadUser(social.getTenantId(), social.getUserId()); | ||||
|  | ||||
|         // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了 | ||||
|         LoginUser loginUser = loginService.buildLoginUser(user); | ||||
|         LoginUser loginUser = TenantHelper.dynamic(social.getTenantId(), () -> { | ||||
|             SysUserVo user = loadUser(social.getUserId()); | ||||
|             // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了 | ||||
|             return loginService.buildLoginUser(user); | ||||
|         }); | ||||
|         loginUser.setClientKey(client.getClientKey()); | ||||
|         loginUser.setDeviceType(client.getDeviceType()); | ||||
|         SaLoginModel model = new SaLoginModel(); | ||||
| @@ -112,20 +116,16 @@ public class SocialAuthStrategy implements IAuthStrategy { | ||||
|         return loginVo; | ||||
|     } | ||||
|  | ||||
|     private SysUserVo loadUser(String tenantId, Long userId) { | ||||
|         return TenantHelper.dynamic(tenantId, () -> { | ||||
|             SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>() | ||||
|                 .select(SysUser::getUserName, SysUser::getStatus) | ||||
|                 .eq(SysUser::getUserId, userId)); | ||||
|             if (ObjectUtil.isNull(user)) { | ||||
|                 log.info("登录用户:{} 不存在.", ""); | ||||
|                 throw new UserException("user.not.exists", ""); | ||||
|             } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { | ||||
|                 log.info("登录用户:{} 已被停用.", ""); | ||||
|                 throw new UserException("user.blocked", ""); | ||||
|             } | ||||
|             return userMapper.selectUserByUserName(user.getUserName()); | ||||
|         }); | ||||
|     private SysUserVo loadUser(Long userId) { | ||||
|         SysUserVo user = userMapper.selectVoById(userId); | ||||
|         if (ObjectUtil.isNull(user)) { | ||||
|             log.info("登录用户:{} 不存在.", ""); | ||||
|             throw new UserException("user.not.exists", ""); | ||||
|         } else if (SystemConstants.DISABLE.equals(user.getStatus())) { | ||||
|             log.info("登录用户:{} 已被停用.", ""); | ||||
|             throw new UserException("user.blocked", ""); | ||||
|         } | ||||
|         return user; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -5,13 +5,21 @@ import cn.dev33.satoken.stp.StpUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import me.zhyd.oauth.config.AuthConfig; | ||||
| import me.zhyd.oauth.model.AuthCallback; | ||||
| import me.zhyd.oauth.model.AuthResponse; | ||||
| import me.zhyd.oauth.model.AuthToken; | ||||
| import me.zhyd.oauth.model.AuthUser; | ||||
| import me.zhyd.oauth.request.AuthRequest; | ||||
| import me.zhyd.oauth.request.AuthWechatMiniProgramRequest; | ||||
| import org.dromara.common.core.constant.SystemConstants; | ||||
| import org.dromara.common.core.domain.model.XcxLoginBody; | ||||
| import org.dromara.common.core.domain.model.XcxLoginUser; | ||||
| import org.dromara.common.core.enums.UserStatus; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
| import org.dromara.common.core.utils.ValidatorUtils; | ||||
| import org.dromara.common.json.utils.JsonUtils; | ||||
| import org.dromara.common.satoken.utils.LoginHelper; | ||||
| import org.dromara.system.domain.SysClient; | ||||
| import org.dromara.system.domain.vo.SysClientVo; | ||||
| import org.dromara.system.domain.vo.SysUserVo; | ||||
| import org.dromara.web.domain.vo.LoginVo; | ||||
| import org.dromara.web.service.IAuthStrategy; | ||||
| @@ -19,7 +27,7 @@ import org.dromara.web.service.SysLoginService; | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| /** | ||||
|  * 邮件认证策略 | ||||
|  * 小程序认证策略 | ||||
|  * | ||||
|  * @author Michelle.Chung | ||||
|  */ | ||||
| @@ -31,7 +39,7 @@ public class XcxAuthStrategy implements IAuthStrategy { | ||||
|     private final SysLoginService loginService; | ||||
|  | ||||
|     @Override | ||||
|     public LoginVo login(String body, SysClient client) { | ||||
|     public LoginVo login(String body, SysClientVo client) { | ||||
|         XcxLoginBody loginBody = JsonUtils.parseObject(body, XcxLoginBody.class); | ||||
|         ValidatorUtils.validate(loginBody); | ||||
|         // xcxCode 为 小程序调用 wx.login 授权后获取 | ||||
| @@ -39,12 +47,24 @@ public class XcxAuthStrategy implements IAuthStrategy { | ||||
|         // 多个小程序识别使用 | ||||
|         String appid = loginBody.getAppid(); | ||||
|  | ||||
|         // todo 以下自行实现 | ||||
|         // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid | ||||
|         String openid = ""; | ||||
|         AuthRequest authRequest = new AuthWechatMiniProgramRequest(AuthConfig.builder() | ||||
|             .clientId(appid).clientSecret("自行填写密钥 可根据不同appid填入不同密钥") | ||||
|             .ignoreCheckRedirectUri(true).ignoreCheckState(true).build()); | ||||
|         AuthCallback authCallback = new AuthCallback(); | ||||
|         authCallback.setCode(xcxCode); | ||||
|         AuthResponse<AuthUser> resp = authRequest.login(authCallback); | ||||
|         String openid, unionId; | ||||
|         if (resp.ok()) { | ||||
|             AuthToken token = resp.getData().getToken(); | ||||
|             openid = token.getOpenId(); | ||||
|             // 微信小程序只有关联到微信开放平台下之后才能获取到 unionId,因此unionId不一定能返回。 | ||||
|             unionId = token.getUnionId(); | ||||
|         } else { | ||||
|             throw new ServiceException(resp.getMsg()); | ||||
|         } | ||||
|         // 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可 | ||||
|         SysUserVo user = loadUserByOpenid(openid); | ||||
|  | ||||
|         // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了 | ||||
|         XcxLoginUser loginUser = new XcxLoginUser(); | ||||
|         loginUser.setTenantId(user.getTenantId()); | ||||
| @@ -81,7 +101,7 @@ public class XcxAuthStrategy implements IAuthStrategy { | ||||
|         if (ObjectUtil.isNull(user)) { | ||||
|             log.info("登录用户:{} 不存在.", openid); | ||||
|             // todo 用户不存在 业务逻辑自行实现 | ||||
|         } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { | ||||
|         } else if (SystemConstants.DISABLE.equals(user.getStatus())) { | ||||
|             log.info("登录用户:{} 已被停用.", openid); | ||||
|             // todo 用户已被停用 业务逻辑自行实现 | ||||
|         } | ||||
|   | ||||
| @@ -5,24 +5,30 @@ spring.boot.admin.client: | ||||
|   url: http://localhost:9090/admin | ||||
|   instance: | ||||
|     service-host-type: IP | ||||
|   username: ruoyi | ||||
|   password: 123456 | ||||
|     metadata: | ||||
|       username: ${spring.boot.admin.client.username} | ||||
|       userpassword: ${spring.boot.admin.client.password} | ||||
|   username: @monitor.username@ | ||||
|   password: @monitor.password@ | ||||
|  | ||||
| --- # powerjob 配置 | ||||
| powerjob: | ||||
|   worker: | ||||
|     # 如何开启调度中心请查看文档教程 | ||||
|     enabled: false | ||||
|     # 需要先在 powerjob 登录页执行应用注册后才能使用 | ||||
|     app-name: ruoyi-worker | ||||
|     allow-lazy-connect-server: false | ||||
|     max-appended-wf-context-length: 4096 | ||||
|     max-result-length: 4096 | ||||
|     # 28080 端口 随着主应用端口飘逸 避免集群冲突 | ||||
|     port: 2${server.port} | ||||
|     protocol: http | ||||
|     server-address: 127.0.0.1:7700 | ||||
|     store-strategy: disk | ||||
| --- # snail-job 配置 | ||||
| snail-job: | ||||
|   enabled: true | ||||
|   # 需要在 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: | ||||
| @@ -43,17 +49,17 @@ spring: | ||||
|           driverClassName: com.mysql.cj.jdbc.Driver | ||||
|           # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 | ||||
|           # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) | ||||
|           url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true | ||||
|           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: root | ||||
|           password: root | ||||
|         # 从库数据源 | ||||
|         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 | ||||
|           username: | ||||
|           password: | ||||
| #        # 从库数据源 | ||||
| #        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 | ||||
| @@ -97,13 +103,14 @@ spring.data: | ||||
|     port: 6379 | ||||
|     # 数据库索引 | ||||
|     database: 0 | ||||
|     # 密码(如没有密码请注释掉) | ||||
|     # password: | ||||
|     # redis 密码必须配置 | ||||
|     password: ruoyi123 | ||||
|     # 连接超时时间 | ||||
|     timeout: 10s | ||||
|     # 是否开启ssl | ||||
|     ssl.enabled: false | ||||
|  | ||||
| # redisson 配置 | ||||
| redisson: | ||||
|   # redis key前缀 | ||||
|   keyPrefix: | ||||
| @@ -113,8 +120,8 @@ redisson: | ||||
|   nettyThreads: 8 | ||||
|   # 单节点配置 | ||||
|   singleServerConfig: | ||||
|     # 客户端名称 | ||||
|     clientName: ${ruoyi.name} | ||||
|     # 客户端名称 不能用中文 | ||||
|     clientName: RuoYi-Vue-Plus | ||||
|     # 最小空闲连接数 | ||||
|     connectionMinimumIdleSize: 8 | ||||
|     # 连接池大小 | ||||
| @@ -149,36 +156,40 @@ mail: | ||||
|   connectionTimeout: 0 | ||||
|  | ||||
| --- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商 | ||||
| # https://wind.kim/doc/start 文档地址 各个厂商可同时使用 | ||||
| # https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用 | ||||
| sms: | ||||
|   # 阿里云 dysmsapi.aliyuncs.com | ||||
|   alibaba: | ||||
|     #请求地址 默认为 dysmsapi.aliyuncs.com 如无特殊改变可以不用设置 | ||||
|     requestUrl: dysmsapi.aliyuncs.com | ||||
|     #阿里云的accessKey | ||||
|     accessKeyId: xxxxxxx | ||||
|     #阿里云的accessKeySecret | ||||
|     accessKeySecret: xxxxxxx | ||||
|     #短信签名 | ||||
|     signature: 测试 | ||||
|   tencent: | ||||
|     #请求地址默认为 sms.tencentcloudapi.com 如无特殊改变可不用设置 | ||||
|     requestUrl: sms.tencentcloudapi.com | ||||
|     #腾讯云的accessKey | ||||
|     accessKeyId: xxxxxxx | ||||
|     #腾讯云的accessKeySecret | ||||
|     accessKeySecret: xxxxxxx | ||||
|     #短信签名 | ||||
|     signature: 测试 | ||||
|     #短信sdkAppId | ||||
|     sdkAppId: appid | ||||
|     #地域信息默认为 ap-guangzhou 如无特殊改变可不用设置 | ||||
|     territory: ap-guangzhou | ||||
|   # 配置源类型用于标定配置来源(interface,yaml) | ||||
|   config-type: yaml | ||||
|   # 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制 | ||||
|   restricted: true | ||||
|   # 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效 | ||||
|   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: 您的accessKey | ||||
|       access-key-secret: 您的accessKeySecret | ||||
|       signature: 您的短信签名 | ||||
|       sdk-app-id: 您的sdkAppId | ||||
|  | ||||
|  | ||||
| --- # 三方授权 | ||||
| justauth: | ||||
|   enabled: true | ||||
|   # 前端外网访问地址 | ||||
|   address: http://localhost:80 | ||||
|   type: | ||||
| @@ -189,6 +200,13 @@ justauth: | ||||
|       client-id: 876892492581044224 | ||||
|       client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8 | ||||
|       redirect-uri: ${justauth.address}/social-callback?source=maxkey | ||||
|     topiam: | ||||
|       # topiam 服务器地址 | ||||
|       server-url: http://127.0.0.1:1898/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 | ||||
|   | ||||
| @@ -8,24 +8,30 @@ spring.boot.admin.client: | ||||
|   url: http://localhost:9090/admin | ||||
|   instance: | ||||
|     service-host-type: IP | ||||
|   username: ruoyi | ||||
|   password: 123456 | ||||
|     metadata: | ||||
|       username: ${spring.boot.admin.client.username} | ||||
|       userpassword: ${spring.boot.admin.client.password} | ||||
|   username: @monitor.username@ | ||||
|   password: @monitor.password@ | ||||
|  | ||||
| --- # powerjob 配置 | ||||
| powerjob: | ||||
|   worker: | ||||
|     # 如何开启调度中心请查看文档教程 | ||||
|     enabled: false | ||||
|     # 需要先在 powerjob 登录页执行应用注册后才能使用 | ||||
|     app-name: ruoyi-worker | ||||
|     allow-lazy-connect-server: false | ||||
|     max-appended-wf-context-length: 4096 | ||||
|     max-result-length: 4096 | ||||
|     # 28080 端口 随着主应用端口飘逸 避免集群冲突 | ||||
|     port: 2${server.port} | ||||
|     protocol: http | ||||
|     server-address: 127.0.0.1:7700 | ||||
|     store-strategy: disk | ||||
| --- # snail-job 配置 | ||||
| snail-job: | ||||
|   enabled: true | ||||
|   # 需要在 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: | ||||
| @@ -46,17 +52,17 @@ spring: | ||||
|           driverClassName: com.mysql.cj.jdbc.Driver | ||||
|           # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 | ||||
|           # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) | ||||
|           url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true | ||||
|           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: root | ||||
|           password: root | ||||
|         # 从库数据源 | ||||
|         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 | ||||
|           username: | ||||
|           password: | ||||
| #        # 从库数据源 | ||||
| #        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 | ||||
| @@ -100,13 +106,14 @@ spring.data: | ||||
|     port: 6379 | ||||
|     # 数据库索引 | ||||
|     database: 0 | ||||
|     # 密码(如没有密码请注释掉) | ||||
|     # password: | ||||
|     # redis 密码必须配置 | ||||
|     password: ruoyi123 | ||||
|     # 连接超时时间 | ||||
|     timeout: 10s | ||||
|     # 是否开启ssl | ||||
|     ssl.enabled: false | ||||
|  | ||||
| # redisson 配置 | ||||
| redisson: | ||||
|   # redis key前缀 | ||||
|   keyPrefix: | ||||
| @@ -116,8 +123,8 @@ redisson: | ||||
|   nettyThreads: 32 | ||||
|   # 单节点配置 | ||||
|   singleServerConfig: | ||||
|     # 客户端名称 | ||||
|     clientName: ${ruoyi.name} | ||||
|     # 客户端名称 不能用中文 | ||||
|     clientName: RuoYi-Vue-Plus | ||||
|     # 最小空闲连接数 | ||||
|     connectionMinimumIdleSize: 32 | ||||
|     # 连接池大小 | ||||
| @@ -152,35 +159,39 @@ mail: | ||||
|   connectionTimeout: 0 | ||||
|  | ||||
| --- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商 | ||||
| # https://wind.kim/doc/start 文档地址 各个厂商可同时使用 | ||||
| # https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用 | ||||
| sms: | ||||
|   # 阿里云 dysmsapi.aliyuncs.com | ||||
|   alibaba: | ||||
|     #请求地址 默认为 dysmsapi.aliyuncs.com 如无特殊改变可以不用设置 | ||||
|     requestUrl: dysmsapi.aliyuncs.com | ||||
|     #阿里云的accessKey | ||||
|     accessKeyId: xxxxxxx | ||||
|     #阿里云的accessKeySecret | ||||
|     accessKeySecret: xxxxxxx | ||||
|     #短信签名 | ||||
|     signature: 测试 | ||||
|   tencent: | ||||
|     #请求地址默认为 sms.tencentcloudapi.com 如无特殊改变可不用设置 | ||||
|     requestUrl: sms.tencentcloudapi.com | ||||
|     #腾讯云的accessKey | ||||
|     accessKeyId: xxxxxxx | ||||
|     #腾讯云的accessKeySecret | ||||
|     accessKeySecret: xxxxxxx | ||||
|     #短信签名 | ||||
|     signature: 测试 | ||||
|     #短信sdkAppId | ||||
|     sdkAppId: appid | ||||
|     #地域信息默认为 ap-guangzhou 如无特殊改变可不用设置 | ||||
|     territory: ap-guangzhou | ||||
|   # 配置源类型用于标定配置来源(interface,yaml) | ||||
|   config-type: yaml | ||||
|   # 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制 | ||||
|   restricted: true | ||||
|   # 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效 | ||||
|   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: 您的accessKey | ||||
|       access-key-secret: 您的accessKeySecret | ||||
|       signature: 您的短信签名 | ||||
|       sdk-app-id: 您的sdkAppId | ||||
|  | ||||
| --- # 三方授权 | ||||
| justauth: | ||||
|   enabled: true | ||||
|   # 前端外网访问地址 | ||||
|   address: http://localhost:80 | ||||
|   type: | ||||
| @@ -191,6 +202,13 @@ justauth: | ||||
|       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 | ||||
|   | ||||
| @@ -1,24 +1,3 @@ | ||||
| # 项目相关配置 | ||||
| ruoyi: | ||||
|   # 名称 | ||||
|   name: RuoYi-Vue-Plus | ||||
|   # 版本 | ||||
|   version: ${revision} | ||||
|   # 版权年份 | ||||
|   copyrightYear: 2023 | ||||
|  | ||||
| captcha: | ||||
|   enable: true | ||||
|   # 页面 <参数设置> 可开启关闭 验证码校验 | ||||
|   # 验证码类型 math 数组计算 char 字符验证 | ||||
|   type: MATH | ||||
|   # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰 | ||||
|   category: CIRCLE | ||||
|   # 数字验证码位数 | ||||
|   numberLength: 1 | ||||
|   # 字符验证码长度 | ||||
|   charLength: 4 | ||||
|  | ||||
| # 开发环境配置 | ||||
| server: | ||||
|   # 服务器的HTTP端口,默认为8080 | ||||
| @@ -41,12 +20,25 @@ server: | ||||
|       # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载 | ||||
|       worker: 256 | ||||
|  | ||||
| captcha: | ||||
|   enable: true | ||||
|   # 页面 <参数设置> 可开启关闭 验证码校验 | ||||
|   # 验证码类型 math 数组计算 char 字符验证 | ||||
|   type: MATH | ||||
|   # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰 | ||||
|   category: CIRCLE | ||||
|   # 数字验证码位数 | ||||
|   numberLength: 1 | ||||
|   # 字符验证码长度 | ||||
|   charLength: 4 | ||||
|  | ||||
| # 日志配置 | ||||
| logging: | ||||
|   level: | ||||
|     org.dromara: @logging.level@ | ||||
|     org.springframework: warn | ||||
|     tech.powerjob.worker.background: warn | ||||
|     org.mybatis.spring.mapper: error | ||||
|     org.apache.fury: warn | ||||
|   config: classpath:logback-plus.xml | ||||
|  | ||||
| # 用户配置 | ||||
| @@ -60,7 +52,11 @@ user: | ||||
| # Spring配置 | ||||
| spring: | ||||
|   application: | ||||
|     name: ${ruoyi.name} | ||||
|     name: RuoYi-Vue-Plus | ||||
|   threads: | ||||
|     # 开启虚拟线程 仅jdk21可用 | ||||
|     virtual: | ||||
|       enabled: false | ||||
|   # 资源信息 | ||||
|   messages: | ||||
|     # 国际化资源文件路径 | ||||
| @@ -75,6 +71,8 @@ spring: | ||||
|       # 设置总上传的文件大小 | ||||
|       max-request-size: 20MB | ||||
|   mvc: | ||||
|     # 设置静态资源路径 防止所有请求都去查静态资源 | ||||
|     static-path-pattern: /static/** | ||||
|     format: | ||||
|       date-time: yyyy-MM-dd HH:mm:ss | ||||
|   jackson: | ||||
| @@ -104,20 +102,15 @@ sa-token: | ||||
| security: | ||||
|   # 排除路径 | ||||
|   excludes: | ||||
|     # 静态资源 | ||||
|     - /*.html | ||||
|     - /**/*.html | ||||
|     - /**/*.css | ||||
|     - /**/*.js | ||||
|     # 公共路径 | ||||
|     - /favicon.ico | ||||
|     - /error | ||||
|     # swagger 文档配置 | ||||
|     - /*/api-docs | ||||
|     - /*/api-docs/** | ||||
|     # actuator 监控配置 | ||||
|     - /actuator | ||||
|     - /actuator/** | ||||
|     - /warm-flow-ui/token-name | ||||
|  | ||||
| # 多租户配置 | ||||
| tenant: | ||||
| @@ -138,8 +131,9 @@ tenant: | ||||
| # MyBatisPlus配置 | ||||
| # https://baomidou.com/config/ | ||||
| mybatis-plus: | ||||
|   # 不支持多包, 如有需要可在注解配置 或 提升扫包等级 | ||||
|   # 例如 com.**.**.mapper | ||||
|   # 自定义配置 是否全局开启逻辑删除 关闭后 所有逻辑删除功能将失效 | ||||
|   enableLogicDelete: true | ||||
|   # 多包名使用 例如 org.dromara.**.mapper,org.xxx.**.mapper | ||||
|   mapperPackage: org.dromara.**.mapper | ||||
|   # 对应的 XML 文件位置 | ||||
|   mapperLocations: classpath*:mapper/**/*Mapper.xml | ||||
| @@ -188,7 +182,7 @@ springdoc: | ||||
| #    persistAuthorization: true | ||||
|   info: | ||||
|     # 标题 | ||||
|     title: '标题:${ruoyi.name}多租户管理系统_接口文档' | ||||
|     title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档' | ||||
|     # 描述 | ||||
|     description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...' | ||||
|     # 版本 | ||||
| @@ -215,17 +209,19 @@ springdoc: | ||||
|       packages-to-scan: org.dromara.system | ||||
|     - group: 4.代码生成模块 | ||||
|       packages-to-scan: org.dromara.generator | ||||
|     - group: 5.工作流模块 | ||||
|       packages-to-scan: org.dromara.workflow | ||||
|  | ||||
| # 防止XSS攻击 | ||||
| xss: | ||||
|   # 过滤开关 | ||||
|   enabled: true | ||||
|   # 排除链接(多个用逗号分隔) | ||||
|   excludes: /system/notice | ||||
|   # 匹配链接 | ||||
|   urlPatterns: /system/*,/monitor/*,/tool/* | ||||
|   excludeUrls: | ||||
|     - /system/notice | ||||
|  | ||||
| # 全局线程池相关配置 | ||||
| # 如使用JDK21请直接使用虚拟线程 不要开启此配置 | ||||
| thread-pool: | ||||
|   # 是否开启线程池 | ||||
|   enabled: false | ||||
| @@ -253,11 +249,33 @@ management: | ||||
|     logfile: | ||||
|       external-file: ./logs/sys-console.log | ||||
|  | ||||
| --- # 默认/推荐使用sse推送 | ||||
| sse: | ||||
|   enabled: true | ||||
|   path: /resource/sse | ||||
|  | ||||
| --- # websocket | ||||
| websocket: | ||||
|   # 如果关闭 需要和前端开关一起关闭 | ||||
|   enabled: true | ||||
|   enabled: false | ||||
|   # 路径 | ||||
|   path: /resource/websocket | ||||
|   # 设置访问源地址 | ||||
|   allowedOrigins: '*' | ||||
|  | ||||
| --- # warm-flow工作流配置 | ||||
| warm-flow: | ||||
|   # 是否开启工作流,默认true | ||||
|   enabled: true | ||||
|   # 是否开启设计器ui | ||||
|   ui: true | ||||
|   # 默认Authorization,如果有多个token,用逗号分隔 | ||||
|   token-name: ${sa-token.token-name},clientid | ||||
|   # 流程状态对应的三元色 | ||||
|   chart-status-color: | ||||
|     ## 未办理 | ||||
|     - 62,62,62 | ||||
|     ## 待办理 | ||||
|     - 255,205,23 | ||||
|     ## 已办理 | ||||
|     - 157,255,0 | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -2,7 +2,7 @@ | ||||
| <configuration> | ||||
|     <property name="log.path" value="./logs"/> | ||||
|     <property name="console.log.pattern" | ||||
|               value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/> | ||||
|               value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/> | ||||
|     <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/> | ||||
|  | ||||
|     <!-- 控制台输出 --> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| package org.dromara.test; | ||||
|  | ||||
| import org.dromara.common.core.config.RuoYiConfig; | ||||
| import org.dromara.common.web.config.properties.CaptchaProperties; | ||||
| import org.junit.jupiter.api.*; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| @@ -17,19 +17,19 @@ import java.util.concurrent.TimeUnit; | ||||
| public class DemoUnitTest { | ||||
|  | ||||
|     @Autowired | ||||
|     private RuoYiConfig ruoYiConfig; | ||||
|     private CaptchaProperties captchaProperties; | ||||
|  | ||||
|     @DisplayName("测试 @SpringBootTest @Test @DisplayName 注解") | ||||
|     @Test | ||||
|     public void testTest() { | ||||
|         System.out.println(ruoYiConfig); | ||||
|         System.out.println(captchaProperties); | ||||
|     } | ||||
|  | ||||
|     @Disabled | ||||
|     @DisplayName("测试 @Disabled 注解") | ||||
|     @Test | ||||
|     public void testDisabled() { | ||||
|         System.out.println(ruoYiConfig); | ||||
|         System.out.println(captchaProperties); | ||||
|     } | ||||
|  | ||||
|     @Timeout(value = 2L, unit = TimeUnit.SECONDS) | ||||
| @@ -37,7 +37,7 @@ public class DemoUnitTest { | ||||
|     @Test | ||||
|     public void testTimeout() throws InterruptedException { | ||||
|         Thread.sleep(3000); | ||||
|         System.out.println(ruoYiConfig); | ||||
|         System.out.println(captchaProperties); | ||||
|     } | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										1
									
								
								ruoyi-admin/zhFonts/.uuid
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								ruoyi-admin/zhFonts/.uuid
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| 3f2ee348-0303-40ca-bf03-03f48d2d2141 | ||||
							
								
								
									
										
											BIN
										
									
								
								ruoyi-admin/zhFonts/SIMSUN.TTC
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								ruoyi-admin/zhFonts/SIMSUN.TTC
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										4
									
								
								ruoyi-admin/zhFonts/fonts.dir
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								ruoyi-admin/zhFonts/fonts.dir
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| 3 | ||||
| SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1 | ||||
| SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1 | ||||
| SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r | ||||
							
								
								
									
										4
									
								
								ruoyi-admin/zhFonts/fonts.scale
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								ruoyi-admin/zhFonts/fonts.scale
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| 3 | ||||
| SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1 | ||||
| SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1 | ||||
| SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r | ||||
| @@ -33,6 +33,7 @@ | ||||
|         <module>ruoyi-common-encrypt</module> | ||||
|         <module>ruoyi-common-tenant</module> | ||||
|         <module>ruoyi-common-websocket</module> | ||||
|         <module>ruoyi-common-sse</module> | ||||
|     </modules> | ||||
|  | ||||
|     <artifactId>ruoyi-common</artifactId> | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
|     </description> | ||||
|  | ||||
|     <properties> | ||||
|         <revision>5.1.2</revision> | ||||
|         <revision>5.3.1</revision> | ||||
|     </properties> | ||||
|  | ||||
|     <dependencyManagement> | ||||
| @@ -172,6 +172,13 @@ | ||||
|                 <version>${revision}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <!-- SSE模块 --> | ||||
|             <dependency> | ||||
|                 <groupId>org.dromara</groupId> | ||||
|                 <artifactId>ruoyi-common-sse</artifactId> | ||||
|                 <version>${revision}</version> | ||||
|             </dependency> | ||||
|  | ||||
|         </dependencies> | ||||
|     </dependencyManagement> | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package org.dromara.common.core.config; | ||||
|  | ||||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | ||||
| import org.springframework.context.annotation.EnableAspectJAutoProxy; | ||||
| import org.springframework.scheduling.annotation.EnableAsync; | ||||
|  | ||||
| /** | ||||
|  * 程序注解配置 | ||||
| @@ -9,8 +10,8 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy; | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @AutoConfiguration | ||||
| // 表示通过aop框架暴露该代理对象,AopContext能够访问 | ||||
| @EnableAspectJAutoProxy(exposeProxy = true) | ||||
| @EnableAspectJAutoProxy | ||||
| @EnableAsync(proxyTargetClass = true) | ||||
| public class ApplicationConfig { | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -5,18 +5,19 @@ 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.core.task.VirtualThreadTaskExecutor; | ||||
| import org.springframework.scheduling.annotation.AsyncConfigurer; | ||||
| import org.springframework.scheduling.annotation.EnableAsync; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.concurrent.Executor; | ||||
|  | ||||
| /** | ||||
|  * 异步配置 | ||||
|  * <p> | ||||
|  * 如果未使用虚拟线程则生效 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @EnableAsync(proxyTargetClass = true) | ||||
| @AutoConfiguration | ||||
| public class AsyncConfig implements AsyncConfigurer { | ||||
|  | ||||
| @@ -25,6 +26,9 @@ public class AsyncConfig implements AsyncConfigurer { | ||||
|      */ | ||||
|     @Override | ||||
|     public Executor getAsyncExecutor() { | ||||
|         if(SpringUtils.isVirtual()) { | ||||
|             return new VirtualThreadTaskExecutor("async-"); | ||||
|         } | ||||
|         return SpringUtils.getBean("scheduledExecutorService"); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,33 +0,0 @@ | ||||
| package org.dromara.common.core.config; | ||||
|  | ||||
| import lombok.Data; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
|  * 读取项目相关配置 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
|  | ||||
| @Data | ||||
| @Component | ||||
| @ConfigurationProperties(prefix = "ruoyi") | ||||
| public class RuoYiConfig { | ||||
|  | ||||
|     /** | ||||
|      * 项目名称 | ||||
|      */ | ||||
|     private String name; | ||||
|  | ||||
|     /** | ||||
|      * 版本 | ||||
|      */ | ||||
|     private String version; | ||||
|  | ||||
|     /** | ||||
|      * 版权年份 | ||||
|      */ | ||||
|     private String copyrightYear; | ||||
|  | ||||
| } | ||||
| @@ -4,11 +4,13 @@ import jakarta.annotation.PreDestroy; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.lang3.concurrent.BasicThreadFactory; | ||||
| import org.dromara.common.core.config.properties.ThreadPoolProperties; | ||||
| import org.dromara.common.core.utils.SpringUtils; | ||||
| import org.dromara.common.core.utils.Threads; | ||||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.core.task.VirtualThreadTaskExecutor; | ||||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||||
|  | ||||
| import java.util.concurrent.ScheduledExecutorService; | ||||
| @@ -49,8 +51,15 @@ public class ThreadPoolConfig { | ||||
|      */ | ||||
|     @Bean(name = "scheduledExecutorService") | ||||
|     protected ScheduledExecutorService scheduledExecutorService() { | ||||
|         // daemon 必须为 true | ||||
|         BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true); | ||||
|         if (SpringUtils.isVirtual()) { | ||||
|             builder.namingPattern("virtual-schedule-pool-%d").wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); | ||||
|         } else { | ||||
|             builder.namingPattern("schedule-pool-%d"); | ||||
|         } | ||||
|         ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core, | ||||
|             new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), | ||||
|             builder.build(), | ||||
|             new ThreadPoolExecutor.CallerRunsPolicy()) { | ||||
|             @Override | ||||
|             protected void afterExecute(Runnable r, Throwable t) { | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package org.dromara.common.core.config; | ||||
| import jakarta.validation.Validator; | ||||
| import org.hibernate.validator.HibernateValidator; | ||||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | ||||
| import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; | ||||
| import org.springframework.context.MessageSource; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; | ||||
| @@ -14,11 +15,11 @@ import java.util.Properties; | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @AutoConfiguration | ||||
| @AutoConfiguration(before = ValidationAutoConfiguration.class) | ||||
| public class ValidatorConfig { | ||||
|  | ||||
|     /** | ||||
|      * 配置校验框架 快速返回模式 | ||||
|      * 配置校验框架 快速失败模式 | ||||
|      */ | ||||
|     @Bean | ||||
|     public Validator validator(MessageSource messageSource) { | ||||
| @@ -28,7 +29,7 @@ public class ValidatorConfig { | ||||
|             // 设置使用 HibernateValidator 校验器 | ||||
|             factoryBean.setProviderClass(HibernateValidator.class); | ||||
|             Properties properties = new Properties(); | ||||
|             // 设置 快速异常返回 | ||||
|             // 设置快速失败模式(fail-fast),即校验过程中一旦遇到失败,立即停止并返回错误 | ||||
|             properties.setProperty("hibernate.validator.fail_fast", "true"); | ||||
|             factoryBean.setValidationProperties(properties); | ||||
|             // 加载配置 | ||||
|   | ||||
| @@ -22,4 +22,9 @@ public interface CacheConstants { | ||||
|      */ | ||||
|     String SYS_DICT_KEY = "sys_dict:"; | ||||
|  | ||||
|     /** | ||||
|      * 登录账户密码错误次数 redis key | ||||
|      */ | ||||
|     String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -30,11 +30,21 @@ public interface CacheNames { | ||||
|      */ | ||||
|     String SYS_DICT = "sys_dict"; | ||||
|  | ||||
|     /** | ||||
|      * 数据字典类型 | ||||
|      */ | ||||
|     String SYS_DICT_TYPE = "sys_dict_type"; | ||||
|  | ||||
|     /** | ||||
|      * 租户 | ||||
|      */ | ||||
|     String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d"; | ||||
|  | ||||
|     /** | ||||
|      * 客户端 | ||||
|      */ | ||||
|     String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d"; | ||||
|  | ||||
|     /** | ||||
|      * 用户账户 | ||||
|      */ | ||||
| @@ -55,6 +65,16 @@ public interface CacheNames { | ||||
|      */ | ||||
|     String SYS_OSS = "sys_oss#30d"; | ||||
|  | ||||
|     /** | ||||
|      * 角色自定义权限 | ||||
|      */ | ||||
|     String SYS_ROLE_CUSTOM = "sys_role_custom#30d"; | ||||
|  | ||||
|     /** | ||||
|      * 部门及以下权限 | ||||
|      */ | ||||
|     String SYS_DEPT_AND_CHILD = "sys_dept_and_child#30d"; | ||||
|  | ||||
|     /** | ||||
|      * OSS配置 | ||||
|      */ | ||||
|   | ||||
| @@ -68,12 +68,7 @@ public interface Constants { | ||||
|     Integer CAPTCHA_EXPIRATION = 2; | ||||
|  | ||||
|     /** | ||||
|      * 令牌 | ||||
|      */ | ||||
|     String TOKEN = "token"; | ||||
|  | ||||
|     /** | ||||
|      * 顶级部门id | ||||
|      * 顶级父级id | ||||
|      */ | ||||
|     Long TOP_PARENT_ID = 0L; | ||||
|  | ||||
|   | ||||
| @@ -27,11 +27,6 @@ public interface GlobalConstants { | ||||
|      */ | ||||
|     String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:"; | ||||
|  | ||||
|     /** | ||||
|      * 登录账户密码错误次数 redis key | ||||
|      */ | ||||
|     String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:"; | ||||
|  | ||||
|     /** | ||||
|      * 三方认证 redis key | ||||
|      */ | ||||
|   | ||||
| @@ -0,0 +1,59 @@ | ||||
| package org.dromara.common.core.constant; | ||||
|  | ||||
| import cn.hutool.core.lang.RegexPool; | ||||
|  | ||||
| /** | ||||
|  * 常用正则表达式字符串 | ||||
|  * <p> | ||||
|  * 常用正则表达式集合,更多正则见: https://any86.github.io/any-rule/ | ||||
|  * | ||||
|  * @author Feng | ||||
|  */ | ||||
| public interface RegexConstants extends RegexPool { | ||||
|  | ||||
|     /** | ||||
|      * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线) | ||||
|      */ | ||||
|     String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$"; | ||||
|  | ||||
|     /** | ||||
|      * 权限标识必须符合以下格式: | ||||
|      * 1. 标准格式:xxx:yyy:zzz | ||||
|      * - 第一部分(xxx):只能包含字母、数字和下划线(_),不能使用 `*` | ||||
|      * - 第二部分(yyy):可以包含字母、数字、下划线(_)和 `*` | ||||
|      * - 第三部分(zzz):可以包含字母、数字、下划线(_)和 `*` | ||||
|      * 2. 允许空字符串(""),表示没有权限标识 | ||||
|      */ | ||||
|     String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$"; | ||||
|  | ||||
|     /** | ||||
|      * 身份证号码(后6位) | ||||
|      */ | ||||
|     String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$"; | ||||
|  | ||||
|     /** | ||||
|      * QQ号码 | ||||
|      */ | ||||
|     String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$"; | ||||
|  | ||||
|     /** | ||||
|      * 邮政编码 | ||||
|      */ | ||||
|     String POSTAL_CODE = "^[1-9]\\d{5}$"; | ||||
|  | ||||
|     /** | ||||
|      * 注册账号 | ||||
|      */ | ||||
|     String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$"; | ||||
|  | ||||
|     /** | ||||
|      * 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符 | ||||
|      */ | ||||
|     String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$"; | ||||
|  | ||||
|     /** | ||||
|      * 通用状态(0表示正常,1表示停用) | ||||
|      */ | ||||
|     String STATUS = "^[01]$"; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,80 @@ | ||||
| package org.dromara.common.core.constant; | ||||
|  | ||||
| /** | ||||
|  * 系统常量信息 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface SystemConstants { | ||||
|  | ||||
|     /** | ||||
|      * 正常状态 | ||||
|      */ | ||||
|     String NORMAL = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 异常状态 | ||||
|      */ | ||||
|     String DISABLE = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 是否为系统默认(是) | ||||
|      */ | ||||
|     String YES = "Y"; | ||||
|  | ||||
|     /** | ||||
|      * 是否为系统默认(否) | ||||
|      */ | ||||
|     String NO = "N"; | ||||
|  | ||||
|     /** | ||||
|      * 是否菜单外链(是) | ||||
|      */ | ||||
|     String YES_FRAME = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 是否菜单外链(否) | ||||
|      */ | ||||
|     String NO_FRAME = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 菜单类型(目录) | ||||
|      */ | ||||
|     String TYPE_DIR = "M"; | ||||
|  | ||||
|     /** | ||||
|      * 菜单类型(菜单) | ||||
|      */ | ||||
|     String TYPE_MENU = "C"; | ||||
|  | ||||
|     /** | ||||
|      * 菜单类型(按钮) | ||||
|      */ | ||||
|     String TYPE_BUTTON = "F"; | ||||
|  | ||||
|     /** | ||||
|      * Layout组件标识 | ||||
|      */ | ||||
|     String LAYOUT = "Layout"; | ||||
|  | ||||
|     /** | ||||
|      * ParentView组件标识 | ||||
|      */ | ||||
|     String PARENT_VIEW = "ParentView"; | ||||
|  | ||||
|     /** | ||||
|      * InnerLink组件标识 | ||||
|      */ | ||||
|     String INNER_LINK = "InnerLink"; | ||||
|  | ||||
|     /** | ||||
|      * 超级管理员ID | ||||
|      */ | ||||
|     Long SUPER_ADMIN_ID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 根部门祖级列表 | ||||
|      */ | ||||
|     String ROOT_DEPT_ANCESTORS = "0"; | ||||
|  | ||||
| } | ||||
| @@ -7,16 +7,6 @@ package org.dromara.common.core.constant; | ||||
|  */ | ||||
| public interface TenantConstants { | ||||
|  | ||||
|     /** | ||||
|      * 租户正常状态 | ||||
|      */ | ||||
|     String NORMAL = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 租户封禁状态 | ||||
|      */ | ||||
|     String DISABLE = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 超级管理员ID | ||||
|      */ | ||||
|   | ||||
| @@ -1,142 +0,0 @@ | ||||
| package org.dromara.common.core.constant; | ||||
|  | ||||
| /** | ||||
|  * 用户常量信息 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| public interface UserConstants { | ||||
|  | ||||
|     /** | ||||
|      * 平台内系统用户的唯一标志 | ||||
|      */ | ||||
|     String SYS_USER = "SYS_USER"; | ||||
|  | ||||
|     /** | ||||
|      * 正常状态 | ||||
|      */ | ||||
|     String NORMAL = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 异常状态 | ||||
|      */ | ||||
|     String EXCEPTION = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 用户正常状态 | ||||
|      */ | ||||
|     String USER_NORMAL = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 用户封禁状态 | ||||
|      */ | ||||
|     String USER_DISABLE = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 角色正常状态 | ||||
|      */ | ||||
|     String ROLE_NORMAL = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 角色封禁状态 | ||||
|      */ | ||||
|     String ROLE_DISABLE = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 部门正常状态 | ||||
|      */ | ||||
|     String DEPT_NORMAL = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 部门停用状态 | ||||
|      */ | ||||
|     String DEPT_DISABLE = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 岗位正常状态 | ||||
|      */ | ||||
|     String POST_NORMAL = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 岗位停用状态 | ||||
|      */ | ||||
|     String POST_DISABLE = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 字典正常状态 | ||||
|      */ | ||||
|     String DICT_NORMAL = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 是否为系统默认(是) | ||||
|      */ | ||||
|     String YES = "Y"; | ||||
|  | ||||
|     /** | ||||
|      * 是否菜单外链(是) | ||||
|      */ | ||||
|     String YES_FRAME = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 是否菜单外链(否) | ||||
|      */ | ||||
|     String NO_FRAME = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 菜单正常状态 | ||||
|      */ | ||||
|     String MENU_NORMAL = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 菜单停用状态 | ||||
|      */ | ||||
|     String MENU_DISABLE = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 菜单类型(目录) | ||||
|      */ | ||||
|     String TYPE_DIR = "M"; | ||||
|  | ||||
|     /** | ||||
|      * 菜单类型(菜单) | ||||
|      */ | ||||
|     String TYPE_MENU = "C"; | ||||
|  | ||||
|     /** | ||||
|      * 菜单类型(按钮) | ||||
|      */ | ||||
|     String TYPE_BUTTON = "F"; | ||||
|  | ||||
|     /** | ||||
|      * Layout组件标识 | ||||
|      */ | ||||
|     String LAYOUT = "Layout"; | ||||
|  | ||||
|     /** | ||||
|      * ParentView组件标识 | ||||
|      */ | ||||
|     String PARENT_VIEW = "ParentView"; | ||||
|  | ||||
|     /** | ||||
|      * InnerLink组件标识 | ||||
|      */ | ||||
|     String INNER_LINK = "InnerLink"; | ||||
|  | ||||
|     /** | ||||
|      * 用户名长度限制 | ||||
|      */ | ||||
|     int USERNAME_MIN_LENGTH = 2; | ||||
|     int USERNAME_MAX_LENGTH = 20; | ||||
|  | ||||
|     /** | ||||
|      * 密码长度限制 | ||||
|      */ | ||||
|     int PASSWORD_MIN_LENGTH = 5; | ||||
|     int PASSWORD_MAX_LENGTH = 20; | ||||
|  | ||||
|     /** | ||||
|      * 超级管理员ID | ||||
|      */ | ||||
|     Long SUPER_ADMIN_ID = 1L; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,71 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * 办理任务请求对象 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| @Data | ||||
| public class CompleteTaskDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 任务id | ||||
|      */ | ||||
|     private Long taskId; | ||||
|  | ||||
|     /** | ||||
|      * 附件id | ||||
|      */ | ||||
|     private String fileId; | ||||
|  | ||||
|     /** | ||||
|      * 抄送人员 | ||||
|      */ | ||||
|     private List<FlowCopyDTO> flowCopyList; | ||||
|  | ||||
|     /** | ||||
|      * 消息类型 | ||||
|      */ | ||||
|     private List<String> messageType; | ||||
|  | ||||
|     /** | ||||
|      * 办理意见 | ||||
|      */ | ||||
|     private String message; | ||||
|  | ||||
|     /** | ||||
|      * 消息通知 | ||||
|      */ | ||||
|     private String notice; | ||||
|  | ||||
|     /** | ||||
|      * 流程变量 | ||||
|      */ | ||||
|     private Map<String, Object> variables; | ||||
|  | ||||
|     /** | ||||
|      * 扩展变量(此处为逗号分隔的ossId) | ||||
|      */ | ||||
|     private String ext; | ||||
|  | ||||
|     public Map<String, Object> getVariables() { | ||||
|         if (variables == null) { | ||||
|             return new HashMap<>(16); | ||||
|         } | ||||
|         variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); | ||||
|         return variables; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,36 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 部门 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class DeptDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 部门ID | ||||
|      */ | ||||
|     private Long deptId; | ||||
|  | ||||
|     /** | ||||
|      * 父部门ID | ||||
|      */ | ||||
|     private Long parentId; | ||||
|  | ||||
|     /** | ||||
|      * 部门名称 | ||||
|      */ | ||||
|     private String deptName; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,41 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 字典数据DTO | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class DictDataDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 字典标签 | ||||
|      */ | ||||
|     private String dictLabel; | ||||
|  | ||||
|     /** | ||||
|      * 字典键值 | ||||
|      */ | ||||
|     private String dictValue; | ||||
|  | ||||
|     /** | ||||
|      * 是否默认(Y是 N否) | ||||
|      */ | ||||
|     private String isDefault; | ||||
|  | ||||
|     /** | ||||
|      * 备注 | ||||
|      */ | ||||
|     private String remark; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,41 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 字典类型DTO | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class DictTypeDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 字典主键 | ||||
|      */ | ||||
|     private Long dictId; | ||||
|  | ||||
|     /** | ||||
|      * 字典名称 | ||||
|      */ | ||||
|     private String dictName; | ||||
|  | ||||
|     /** | ||||
|      * 字典类型 | ||||
|      */ | ||||
|     private String dictType; | ||||
|  | ||||
|     /** | ||||
|      * 备注 | ||||
|      */ | ||||
|     private String remark; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,30 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * 抄送 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| @Data | ||||
| public class FlowCopyDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 用户id | ||||
|      */ | ||||
|     private Long userId; | ||||
|  | ||||
|     /** | ||||
|      * 用户名称 | ||||
|      */ | ||||
|     private String userName; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,46 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * OSS对象 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class OssDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 对象存储主键 | ||||
|      */ | ||||
|     private Long ossId; | ||||
|  | ||||
|     /** | ||||
|      * 文件名 | ||||
|      */ | ||||
|     private String fileName; | ||||
|  | ||||
|     /** | ||||
|      * 原名 | ||||
|      */ | ||||
|     private String originalName; | ||||
|  | ||||
|     /** | ||||
|      * 文件后缀名 | ||||
|      */ | ||||
|     private String fileSuffix; | ||||
|  | ||||
|     /** | ||||
|      * URL地址 | ||||
|      */ | ||||
|     private String url; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,46 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 岗位 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class PostDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 岗位ID | ||||
|      */ | ||||
|     private Long postId; | ||||
|  | ||||
|     /** | ||||
|      * 部门id | ||||
|      */ | ||||
|     private Long deptId; | ||||
|  | ||||
|     /** | ||||
|      * 岗位编码 | ||||
|      */ | ||||
|     private String postCode; | ||||
|  | ||||
|     /** | ||||
|      * 岗位名称 | ||||
|      */ | ||||
|     private String postName; | ||||
|  | ||||
|     /** | ||||
|      * 岗位类别编码 | ||||
|      */ | ||||
|     private String postCategory; | ||||
|  | ||||
| } | ||||
| @@ -3,6 +3,7 @@ package org.dromara.common.core.domain.dto; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
| @@ -15,6 +16,9 @@ import java.io.Serializable; | ||||
| @NoArgsConstructor | ||||
| public class RoleDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 角色ID | ||||
|      */ | ||||
| @@ -31,7 +35,7 @@ public class RoleDTO implements Serializable { | ||||
|     private String roleKey; | ||||
|  | ||||
|     /** | ||||
|      * 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) | ||||
|      * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限) | ||||
|      */ | ||||
|     private String dataScope; | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,45 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * 启动流程对象 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| @Data | ||||
| public class StartProcessDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 业务唯一值id | ||||
|      */ | ||||
|     private String businessId; | ||||
|  | ||||
|     /** | ||||
|      * 流程定义编码 | ||||
|      */ | ||||
|     private String flowCode; | ||||
|  | ||||
|     /** | ||||
|      * 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}} | ||||
|      */ | ||||
|     private Map<String, Object> variables; | ||||
|  | ||||
|     public Map<String, Object> getVariables() { | ||||
|         if (variables == null) { | ||||
|             return new HashMap<>(16); | ||||
|         } | ||||
|         variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); | ||||
|         return variables; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,30 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 启动流程返回对象 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| public class StartProcessReturnDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 流程实例id | ||||
|      */ | ||||
|     private Long processInstanceId; | ||||
|  | ||||
|     /** | ||||
|      * 任务id | ||||
|      */ | ||||
|     private Long taskId; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,101 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| import java.util.function.Function; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| /** | ||||
|  * 任务受让人 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class TaskAssigneeDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 总大小 | ||||
|      */ | ||||
|     private Long total = 0L; | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      */ | ||||
|     private List<TaskHandler> list; | ||||
|  | ||||
|     public TaskAssigneeDTO(Long total, List<TaskHandler> list) { | ||||
|         this.total = total; | ||||
|         this.list = list; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将源列表转换为 TaskHandler 列表 | ||||
|      * | ||||
|      * @param <T>              通用类型 | ||||
|      * @param sourceList       待转换的源列表 | ||||
|      * @param storageId        提取 storageId 的函数 | ||||
|      * @param handlerCode      提取 handlerCode 的函数 | ||||
|      * @param handlerName      提取 handlerName 的函数 | ||||
|      * @param groupName        提取 groupName 的函数 | ||||
|      * @param createTimeMapper 提取 createTime 的函数 | ||||
|      * @return 转换后的 TaskHandler 列表 | ||||
|      */ | ||||
|     public static <T> List<TaskHandler> convertToHandlerList( | ||||
|         List<T> sourceList, | ||||
|         Function<T, Long> storageId, | ||||
|         Function<T, String> handlerCode, | ||||
|         Function<T, String> handlerName, | ||||
|         Function<T, Long> groupName, | ||||
|         Function<T, Date> createTimeMapper) { | ||||
|         return sourceList.stream() | ||||
|             .map(item -> new TaskHandler( | ||||
|                 String.valueOf(storageId.apply(item)), | ||||
|                 handlerCode.apply(item), | ||||
|                 handlerName.apply(item), | ||||
|                 groupName != null ? String.valueOf(groupName.apply(item)) : null, | ||||
|                 createTimeMapper.apply(item) | ||||
|             )).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     @NoArgsConstructor | ||||
|     @AllArgsConstructor | ||||
|     public static class TaskHandler { | ||||
|  | ||||
|         /** | ||||
|          * 主键 | ||||
|          */ | ||||
|         private String storageId; | ||||
|  | ||||
|         /** | ||||
|          * 权限编码 | ||||
|          */ | ||||
|         private String handlerCode; | ||||
|  | ||||
|         /** | ||||
|          * 权限名称 | ||||
|          */ | ||||
|         private String handlerName; | ||||
|  | ||||
|         /** | ||||
|          * 权限分组 | ||||
|          */ | ||||
|         private String groupName; | ||||
|  | ||||
|         /** | ||||
|          * 创建时间 | ||||
|          */ | ||||
|         private Date createTime; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,73 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.Date; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * 用户 | ||||
|  * | ||||
|  * @author Michelle.Chung | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class UserDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 用户ID | ||||
|      */ | ||||
|     private Long userId; | ||||
|  | ||||
|     /** | ||||
|      * 部门ID | ||||
|      */ | ||||
|     private Long deptId; | ||||
|  | ||||
|     /** | ||||
|      * 用户账号 | ||||
|      */ | ||||
|     private String userName; | ||||
|  | ||||
|     /** | ||||
|      * 用户昵称 | ||||
|      */ | ||||
|     private String nickName; | ||||
|  | ||||
|     /** | ||||
|      * 用户类型(sys_user系统用户) | ||||
|      */ | ||||
|     private String userType; | ||||
|  | ||||
|     /** | ||||
|      * 用户邮箱 | ||||
|      */ | ||||
|     private String email; | ||||
|  | ||||
|     /** | ||||
|      * 手机号码 | ||||
|      */ | ||||
|     private String phonenumber; | ||||
|  | ||||
|     /** | ||||
|      * 用户性别(0男 1女 2未知) | ||||
|      */ | ||||
|     private String sex; | ||||
|  | ||||
|     /** | ||||
|      * 帐号状态(0正常 1停用) | ||||
|      */ | ||||
|     private String status; | ||||
|  | ||||
|     /** | ||||
|      * 创建时间 | ||||
|      */ | ||||
|     private Date createTime; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,44 @@ | ||||
| package org.dromara.common.core.domain.event; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 流程创建任务监听 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| @Data | ||||
| public class ProcessCreateTaskEvent implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 租户ID | ||||
|      */ | ||||
|     private String tenantId; | ||||
|  | ||||
|     /** | ||||
|      * 流程定义编码 | ||||
|      */ | ||||
|     private String flowCode; | ||||
|  | ||||
|     /** | ||||
|      * 审批节点编码 | ||||
|      */ | ||||
|     private String nodeCode; | ||||
|  | ||||
|     /** | ||||
|      * 任务id | ||||
|      */ | ||||
|     private Long taskId; | ||||
|  | ||||
|     /** | ||||
|      * 业务id | ||||
|      */ | ||||
|     private String businessId; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,34 @@ | ||||
| package org.dromara.common.core.domain.event; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 删除流程监听 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| @Data | ||||
| public class ProcessDeleteEvent implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 租户ID | ||||
|      */ | ||||
|     private String tenantId; | ||||
|  | ||||
|     /** | ||||
|      * 流程定义编码 | ||||
|      */ | ||||
|     private String flowCode; | ||||
|  | ||||
|     /** | ||||
|      * 业务id | ||||
|      */ | ||||
|     private String businessId; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,50 @@ | ||||
| package org.dromara.common.core.domain.event; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * 总体流程监听 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| @Data | ||||
| public class ProcessEvent implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 租户ID | ||||
|      */ | ||||
|     private String tenantId; | ||||
|  | ||||
|     /** | ||||
|      * 流程定义编码 | ||||
|      */ | ||||
|     private String flowCode; | ||||
|  | ||||
|     /** | ||||
|      * 业务id | ||||
|      */ | ||||
|     private String businessId; | ||||
|  | ||||
|     /** | ||||
|      * 状态 | ||||
|      */ | ||||
|     private String status; | ||||
|  | ||||
|     /** | ||||
|      * 办理参数 | ||||
|      */ | ||||
|     private Map<String, Object> params; | ||||
|  | ||||
|     /** | ||||
|      * 当为true时为申请人节点办理 | ||||
|      */ | ||||
|     private boolean submit; | ||||
|  | ||||
| } | ||||
| @@ -1,8 +1,9 @@ | ||||
| package org.dromara.common.core.domain.model; | ||||
|  | ||||
| import org.dromara.common.core.domain.dto.RoleDTO; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.dromara.common.core.domain.dto.PostDTO; | ||||
| import org.dromara.common.core.domain.dto.RoleDTO; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| @@ -14,7 +15,6 @@ import java.util.Set; | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
|  | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class LoginUser implements Serializable { | ||||
| @@ -37,6 +37,11 @@ public class LoginUser implements Serializable { | ||||
|      */ | ||||
|     private Long deptId; | ||||
|  | ||||
|     /** | ||||
|      * 部门类别编码 | ||||
|      */ | ||||
|     private String deptCategory; | ||||
|  | ||||
|     /** | ||||
|      * 部门名 | ||||
|      */ | ||||
| @@ -107,6 +112,11 @@ public class LoginUser implements Serializable { | ||||
|      */ | ||||
|     private List<RoleDTO> roles; | ||||
|  | ||||
|     /** | ||||
|      * 岗位对象 | ||||
|      */ | ||||
|     private List<PostDTO> posts; | ||||
|  | ||||
|     /** | ||||
|      * 数据权限 当前角色ID | ||||
|      */ | ||||
|   | ||||
| @@ -5,8 +5,6 @@ import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import org.hibernate.validator.constraints.Length; | ||||
|  | ||||
| import static org.dromara.common.core.constant.UserConstants.*; | ||||
|  | ||||
| /** | ||||
|  * 密码登录对象 | ||||
|  * | ||||
| @@ -20,14 +18,14 @@ public class PasswordLoginBody extends LoginBody { | ||||
|      * 用户名 | ||||
|      */ | ||||
|     @NotBlank(message = "{user.username.not.blank}") | ||||
|     @Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}") | ||||
|     @Length(min = 2, max = 30, message = "{user.username.length.valid}") | ||||
|     private String username; | ||||
|  | ||||
|     /** | ||||
|      * 用户密码 | ||||
|      */ | ||||
|     @NotBlank(message = "{user.password.not.blank}") | ||||
|     @Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}") | ||||
|     @Length(min = 5, max = 30, message = "{user.password.length.valid}") | ||||
|     private String password; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -5,8 +5,6 @@ import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import org.hibernate.validator.constraints.Length; | ||||
|  | ||||
| import static org.dromara.common.core.constant.UserConstants.*; | ||||
|  | ||||
| /** | ||||
|  * 用户注册对象 | ||||
|  * | ||||
| @@ -20,14 +18,14 @@ public class RegisterBody extends LoginBody { | ||||
|      * 用户名 | ||||
|      */ | ||||
|     @NotBlank(message = "{user.username.not.blank}") | ||||
|     @Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}") | ||||
|     @Length(min = 2, max = 20, message = "{user.username.length.valid}") | ||||
|     private String username; | ||||
|  | ||||
|     /** | ||||
|      * 用户密码 | ||||
|      */ | ||||
|     @NotBlank(message = "{user.password.not.blank}") | ||||
|     @Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}") | ||||
|     @Length(min = 5, max = 20, message = "{user.password.length.valid}") | ||||
|     private String password; | ||||
|  | ||||
|     private String userType; | ||||
|   | ||||
| @@ -0,0 +1,56 @@ | ||||
| package org.dromara.common.core.domain.model; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 任务受让人 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class TaskAssigneeBody implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 权限编码 | ||||
|      */ | ||||
|     private String handlerCode; | ||||
|  | ||||
|     /** | ||||
|      * 权限名称 | ||||
|      */ | ||||
|     private String handlerName; | ||||
|  | ||||
|     /** | ||||
|      * 权限分组 | ||||
|      */ | ||||
|     private String groupId; | ||||
|  | ||||
|     /** | ||||
|      * 开始时间 | ||||
|      */ | ||||
|     private String beginTime; | ||||
|  | ||||
|     /** | ||||
|      * 结束时间 | ||||
|      */ | ||||
|     private String endTime; | ||||
|  | ||||
|     /** | ||||
|      * 当前页 | ||||
|      */ | ||||
|     private Integer pageNum = 1; | ||||
|  | ||||
|     /** | ||||
|      * 每页显示条数 | ||||
|      */ | ||||
|     private Integer pageSize = 10; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,215 @@ | ||||
| package org.dromara.common.core.enums; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.function.Function; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| /** | ||||
|  * 业务状态枚举 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum BusinessStatusEnum { | ||||
|  | ||||
|     /** | ||||
|      * 已撤销 | ||||
|      */ | ||||
|     CANCEL("cancel", "已撤销"), | ||||
|  | ||||
|     /** | ||||
|      * 草稿 | ||||
|      */ | ||||
|     DRAFT("draft", "草稿"), | ||||
|  | ||||
|     /** | ||||
|      * 待审核 | ||||
|      */ | ||||
|     WAITING("waiting", "待审核"), | ||||
|  | ||||
|     /** | ||||
|      * 已完成 | ||||
|      */ | ||||
|     FINISH("finish", "已完成"), | ||||
|  | ||||
|     /** | ||||
|      * 已作废 | ||||
|      */ | ||||
|     INVALID("invalid", "已作废"), | ||||
|  | ||||
|     /** | ||||
|      * 已退回 | ||||
|      */ | ||||
|     BACK("back", "已退回"), | ||||
|  | ||||
|     /** | ||||
|      * 已终止 | ||||
|      */ | ||||
|     TERMINATION("termination", "已终止"); | ||||
|  | ||||
|     /** | ||||
|      * 状态 | ||||
|      */ | ||||
|     private final String status; | ||||
|  | ||||
|     /** | ||||
|      * 描述 | ||||
|      */ | ||||
|     private final String desc; | ||||
|  | ||||
|     private static final Map<String, BusinessStatusEnum> STATUS_MAP = Arrays.stream(BusinessStatusEnum.values()) | ||||
|         .collect(Collectors.toConcurrentMap(BusinessStatusEnum::getStatus, Function.identity())); | ||||
|  | ||||
|     /** | ||||
|      * 根据状态获取对应的 BusinessStatusEnum 枚举 | ||||
|      * | ||||
|      * @param status 业务状态码 | ||||
|      * @return 对应的 BusinessStatusEnum 枚举,如果找不到则返回 null | ||||
|      */ | ||||
|     public static BusinessStatusEnum getByStatus(String status) { | ||||
|         // 使用 STATUS_MAP 获取对应的枚举,若找不到则返回 null | ||||
|         return STATUS_MAP.get(status); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据状态获取对应的业务状态描述信息 | ||||
|      * | ||||
|      * @param status 业务状态码 | ||||
|      * @return 返回业务状态描述,若状态码为空或未找到对应的枚举,返回空字符串 | ||||
|      */ | ||||
|     public static String findByStatus(String status) { | ||||
|         if (StringUtils.isBlank(status)) { | ||||
|             return StrUtil.EMPTY; | ||||
|         } | ||||
|         BusinessStatusEnum statusEnum = STATUS_MAP.get(status); | ||||
|         return (statusEnum != null) ? statusEnum.getDesc() : StrUtil.EMPTY; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 判断是否为指定的状态之一:草稿、已撤销或已退回 | ||||
|      * | ||||
|      * @param status 要检查的状态 | ||||
|      * @return 如果状态为草稿、已撤销或已退回之一,则返回 true;否则返回 false | ||||
|      */ | ||||
|     public static boolean isDraftOrCancelOrBack(String status) { | ||||
|         return DRAFT.status.equals(status) || CANCEL.status.equals(status) || BACK.status.equals(status); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 判断是否为撤销,退回,作废,终止 | ||||
|      * | ||||
|      * @param status status | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     public static boolean initialState(String status) { | ||||
|         return CANCEL.status.equals(status) || BACK.status.equals(status) || INVALID.status.equals(status) || TERMINATION.status.equals(status); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取运行中的实例状态列表 | ||||
|      * | ||||
|      * @return 包含运行中实例状态的不可变列表 | ||||
|      * (包含 DRAFT、WAITING、BACK 和 CANCEL 状态) | ||||
|      */ | ||||
|     public static List<String> runningStatus() { | ||||
|         return Arrays.asList(DRAFT.status, WAITING.status, BACK.status, CANCEL.status); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取结束实例的状态列表 | ||||
|      * | ||||
|      * @return 包含结束实例状态的不可变列表 | ||||
|      * (包含 FINISH、INVALID 和 TERMINATION 状态) | ||||
|      */ | ||||
|     public static List<String> finishStatus() { | ||||
|         return Arrays.asList(FINISH.status, INVALID.status, TERMINATION.status); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 启动流程校验 | ||||
|      * | ||||
|      * @param status 状态 | ||||
|      */ | ||||
|     public static void checkStartStatus(String status) { | ||||
|         if (WAITING.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已提交过申请,正在审批中!"); | ||||
|         } else if (FINISH.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已完成申请!"); | ||||
|         } else if (INVALID.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已作废!"); | ||||
|         } else if (TERMINATION.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已终止!"); | ||||
|         } else if (StringUtils.isBlank(status)) { | ||||
|             throw new ServiceException("流程状态为空!"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 撤销流程校验 | ||||
|      * | ||||
|      * @param status 状态 | ||||
|      */ | ||||
|     public static void checkCancelStatus(String status) { | ||||
|         if (CANCEL.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已撤销!"); | ||||
|         } else if (FINISH.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已完成申请!"); | ||||
|         } else if (INVALID.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已作废!"); | ||||
|         } else if (TERMINATION.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已终止!"); | ||||
|         } else if (BACK.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已退回!"); | ||||
|         } else if (StringUtils.isBlank(status)) { | ||||
|             throw new ServiceException("流程状态为空!"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 驳回流程校验 | ||||
|      * | ||||
|      * @param status 状态 | ||||
|      */ | ||||
|     public static void checkBackStatus(String status) { | ||||
|         if (BACK.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已退回!"); | ||||
|         } else if (FINISH.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已完成申请!"); | ||||
|         } else if (INVALID.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已作废!"); | ||||
|         } else if (TERMINATION.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已终止!"); | ||||
|         } else if (CANCEL.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已撤销!"); | ||||
|         } else if (StringUtils.isBlank(status)) { | ||||
|             throw new ServiceException("流程状态为空!"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 作废,终止流程校验 | ||||
|      * | ||||
|      * @param status 状态 | ||||
|      */ | ||||
|     public static void checkInvalidStatus(String status) { | ||||
|         if (FINISH.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已完成申请!"); | ||||
|         } else if (INVALID.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已作废!"); | ||||
|         } else if (TERMINATION.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已终止!"); | ||||
|         } else if (StringUtils.isBlank(status)) { | ||||
|             throw new ServiceException("流程状态为空!"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,146 @@ | ||||
| package org.dromara.common.core.enums; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
|  | ||||
| /* | ||||
|  * 日期格式 | ||||
|  * "yyyy":4位数的年份,例如:2023年表示为"2023"。 | ||||
|  * "yy":2位数的年份,例如:2023年表示为"23"。 | ||||
|  * "MM":2位数的月份,取值范围为01到12,例如:7月表示为"07"。 | ||||
|  * "M":不带前导零的月份,取值范围为1到12,例如:7月表示为"7"。 | ||||
|  * "dd":2位数的日期,取值范围为01到31,例如:22日表示为"22"。 | ||||
|  * "d":不带前导零的日期,取值范围为1到31,例如:22日表示为"22"。 | ||||
|  * "EEEE":星期的全名,例如:星期三表示为"Wednesday"。 | ||||
|  * "E":星期的缩写,例如:星期三表示为"Wed"。 | ||||
|  * "DDD" 或 "D":一年中的第几天,取值范围为001到366,例如:第200天表示为"200"。 | ||||
|  * 时间格式 | ||||
|  * "HH":24小时制的小时数,取值范围为00到23,例如:下午5点表示为"17"。 | ||||
|  * "hh":12小时制的小时数,取值范围为01到12,例如:下午5点表示为"05"。 | ||||
|  * "mm":分钟数,取值范围为00到59,例如:30分钟表示为"30"。 | ||||
|  * "ss":秒数,取值范围为00到59,例如:45秒表示为"45"。 | ||||
|  * "SSS":毫秒数,取值范围为000到999,例如:123毫秒表示为"123"。 | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * 日期格式与时间格式枚举 | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum FormatsType { | ||||
|  | ||||
|     /** | ||||
|      * 例如:2023年表示为"23" | ||||
|      */ | ||||
|     YY("yy"), | ||||
|  | ||||
|     /** | ||||
|      * 例如:2023年表示为"2023" | ||||
|      */ | ||||
|     YYYY("yyyy"), | ||||
|  | ||||
|     /** | ||||
|      * 例例如,2023年7月可以表示为 "2023-07" | ||||
|      */ | ||||
|     YYYY_MM("yyyy-MM"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,日期 "2023年7月22日" 可以表示为 "2023-07-22" | ||||
|      */ | ||||
|     YYYY_MM_DD("yyyy-MM-dd"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023-07-22 15:30" | ||||
|      */ | ||||
|     YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023-07-22 15:30:45" | ||||
|      */ | ||||
|     YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"), | ||||
|  | ||||
|     /** | ||||
|      * 例如:下午3点30分45秒,表示为 "15:30:45" | ||||
|      */ | ||||
|     HH_MM_SS("HH:mm:ss"), | ||||
|  | ||||
|     /** | ||||
|      * 例例如,2023年7月可以表示为 "2023/07" | ||||
|      */ | ||||
|     YYYY_MM_SLASH("yyyy/MM"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,日期 "2023年7月22日" 可以表示为 "2023/07/22" | ||||
|      */ | ||||
|     YYYY_MM_DD_SLASH("yyyy/MM/dd"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45" | ||||
|      */ | ||||
|     YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45" | ||||
|      */ | ||||
|     YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"), | ||||
|  | ||||
|     /** | ||||
|      * 例例如,2023年7月可以表示为 "2023.07" | ||||
|      */ | ||||
|     YYYY_MM_DOT("yyyy.MM"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,日期 "2023年7月22日" 可以表示为 "2023.07.22" | ||||
|      */ | ||||
|     YYYY_MM_DD_DOT("yyyy.MM.dd"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023.07.22 15:30" | ||||
|      */ | ||||
|     YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023.07.22 15:30:45" | ||||
|      */ | ||||
|     YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,2023年7月可以表示为 "202307" | ||||
|      */ | ||||
|     YYYYMM("yyyyMM"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,2023年7月22日可以表示为 "20230722" | ||||
|      */ | ||||
|     YYYYMMDD("yyyyMMdd"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,2023年7月22日下午3点可以表示为 "2023072215" | ||||
|      */ | ||||
|     YYYYMMDDHH("yyyyMMddHH"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,2023年7月22日下午3点30分可以表示为 "202307221530" | ||||
|      */ | ||||
|     YYYYMMDDHHMM("yyyyMMddHHmm"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,2023年7月22日下午3点30分45秒可以表示为 "20230722153045" | ||||
|      */ | ||||
|     YYYYMMDDHHMMSS("yyyyMMddHHmmss"); | ||||
|  | ||||
|     /** | ||||
|      * 时间格式 | ||||
|      */ | ||||
|     private final String timeFormat; | ||||
|  | ||||
|     public static FormatsType getFormatsType(String str) { | ||||
|         for (FormatsType value : values()) { | ||||
|             if (StringUtils.contains(str, value.getTimeFormat())) { | ||||
|                 return value; | ||||
|             } | ||||
|         } | ||||
|         throw new RuntimeException("'FormatsType' not found By " + str); | ||||
|     } | ||||
| } | ||||
| @@ -1,30 +0,0 @@ | ||||
| package org.dromara.common.core.enums; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
|  | ||||
| /** | ||||
|  * 用户状态 | ||||
|  * | ||||
|  * @author LionLi | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum TenantStatus { | ||||
|     /** | ||||
|      * 正常 | ||||
|      */ | ||||
|     OK("0", "正常"), | ||||
|     /** | ||||
|      * 停用 | ||||
|      */ | ||||
|     DISABLE("1", "停用"), | ||||
|     /** | ||||
|      * 删除 | ||||
|      */ | ||||
|     DELETED("2", "删除"); | ||||
|  | ||||
|     private final String code; | ||||
|     private final String info; | ||||
|  | ||||
| } | ||||
| @@ -1,9 +1,6 @@ | ||||
| package org.dromara.common.core.exception; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.*; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| @@ -45,19 +42,11 @@ public final class ServiceException extends RuntimeException { | ||||
|         this.code = code; | ||||
|     } | ||||
|  | ||||
|     public String getDetailMessage() { | ||||
|         return detailMessage; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getMessage() { | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
|     public Integer getCode() { | ||||
|         return code; | ||||
|     } | ||||
|  | ||||
|     public ServiceException setMessage(String message) { | ||||
|         this.message = message; | ||||
|         return this; | ||||
|   | ||||
| @@ -0,0 +1,62 @@ | ||||
| package org.dromara.common.core.exception; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| /** | ||||
|  * sse 特制异常 | ||||
|  * | ||||
|  * @author LionLi | ||||
|  */ | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public final class SseException extends RuntimeException { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 错误码 | ||||
|      */ | ||||
|     private Integer code; | ||||
|  | ||||
|     /** | ||||
|      * 错误提示 | ||||
|      */ | ||||
|     private String message; | ||||
|  | ||||
|     /** | ||||
|      * 错误明细,内部调试错误 | ||||
|      */ | ||||
|     private String detailMessage; | ||||
|  | ||||
|     public SseException(String message) { | ||||
|         this.message = message; | ||||
|     } | ||||
|  | ||||
|     public SseException(String message, Integer code) { | ||||
|         this.message = message; | ||||
|         this.code = code; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getMessage() { | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
|     public SseException setMessage(String message) { | ||||
|         this.message = message; | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     public SseException setDetailMessage(String detailMessage) { | ||||
|         this.detailMessage = detailMessage; | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,52 @@ | ||||
| package org.dromara.common.core.factory; | ||||
|  | ||||
| import cn.hutool.core.lang.PatternPool; | ||||
| import org.dromara.common.core.constant.RegexConstants; | ||||
|  | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| /** | ||||
|  * 正则表达式模式池工厂 | ||||
|  * <p>初始化的时候将正则表达式加入缓存池当中</p> | ||||
|  * <p>提高正则表达式的性能,避免重复编译相同的正则表达式</p> | ||||
|  * | ||||
|  * @author 21001 | ||||
|  */ | ||||
| public class RegexPatternPoolFactory extends PatternPool { | ||||
|  | ||||
|     /** | ||||
|      * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线) | ||||
|      */ | ||||
|     public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE); | ||||
|  | ||||
|     /** | ||||
|      * 身份证号码(后6位) | ||||
|      */ | ||||
|     public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6); | ||||
|  | ||||
|     /** | ||||
|      * QQ号码 | ||||
|      */ | ||||
|     public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER); | ||||
|  | ||||
|     /** | ||||
|      * 邮政编码 | ||||
|      */ | ||||
|     public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE); | ||||
|  | ||||
|     /** | ||||
|      * 注册账号 | ||||
|      */ | ||||
|     public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT); | ||||
|  | ||||
|     /** | ||||
|      * 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符 | ||||
|      */ | ||||
|     public static final Pattern PASSWORD = get(RegexConstants.PASSWORD); | ||||
|  | ||||
|     /** | ||||
|      * 通用状态(0表示正常,1表示停用) | ||||
|      */ | ||||
|     public static final Pattern STATUS = get(RegexConstants.STATUS); | ||||
|  | ||||
| } | ||||
| @@ -1,5 +1,9 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| import org.dromara.common.core.domain.dto.DeptDTO; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 通用 部门服务 | ||||
|  * | ||||
| @@ -15,4 +19,19 @@ public interface DeptService { | ||||
|      */ | ||||
|     String selectDeptNameByIds(String deptIds); | ||||
|  | ||||
|     /** | ||||
|      * 根据部门ID查询部门负责人 | ||||
|      * | ||||
|      * @param deptId 部门ID,用于指定需要查询的部门 | ||||
|      * @return 返回该部门的负责人ID | ||||
|      */ | ||||
|     Long selectDeptLeaderById(Long deptId); | ||||
|  | ||||
|     /** | ||||
|      * 查询部门 | ||||
|      * | ||||
|      * @return 部门列表 | ||||
|      */ | ||||
|     List<DeptDTO> selectDeptsByList(); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,9 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| import org.dromara.common.core.domain.dto.DictDataDTO; | ||||
| import org.dromara.common.core.domain.dto.DictTypeDTO; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
| @@ -64,4 +68,20 @@ public interface DictService { | ||||
|      */ | ||||
|     Map<String, String> getAllDictByDictType(String dictType); | ||||
|  | ||||
|     /** | ||||
|      * 根据字典类型查询详细信息 | ||||
|      * | ||||
|      * @param dictType 字典类型 | ||||
|      * @return 字典类型详细信息 | ||||
|      */ | ||||
|     DictTypeDTO getDictType(String dictType); | ||||
|  | ||||
|     /** | ||||
|      * 根据字典类型查询字典数据列表 | ||||
|      * | ||||
|      * @param dictType 字典类型 | ||||
|      * @return 字典数据列表 | ||||
|      */ | ||||
|     List<DictDataDTO> getDictData(String dictType); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,9 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| import org.dromara.common.core.domain.dto.OssDTO; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 通用 OSS服务 | ||||
|  * | ||||
| @@ -15,4 +19,11 @@ public interface OssService { | ||||
|      */ | ||||
|     String selectUrlByIds(String ossIds); | ||||
|  | ||||
|     /** | ||||
|      * 通过ossId查询列表 | ||||
|      * | ||||
|      * @param ossIds ossId串逗号分隔 | ||||
|      * @return 列表 | ||||
|      */ | ||||
|     List<OssDTO> selectByIds(String ossIds); | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,10 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| /** | ||||
|  * 通用 岗位服务 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| public interface PostService { | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,10 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| /** | ||||
|  * 通用 角色服务 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| public interface RoleService { | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,45 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| import org.dromara.common.core.domain.dto.TaskAssigneeDTO; | ||||
| import org.dromara.common.core.domain.model.TaskAssigneeBody; | ||||
|  | ||||
| /** | ||||
|  * 工作流设计器获取任务执行人 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface TaskAssigneeService { | ||||
|  | ||||
|     /** | ||||
|      * 查询角色并返回任务指派的列表,支持分页 | ||||
|      * | ||||
|      * @param taskQuery 查询条件 | ||||
|      * @return 办理人 | ||||
|      */ | ||||
|     TaskAssigneeDTO selectRolesByTaskAssigneeList(TaskAssigneeBody taskQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查询岗位并返回任务指派的列表,支持分页 | ||||
|      * | ||||
|      * @param taskQuery 查询条件 | ||||
|      * @return 办理人 | ||||
|      */ | ||||
|     TaskAssigneeDTO selectPostsByTaskAssigneeList(TaskAssigneeBody taskQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查询部门并返回任务指派的列表,支持分页 | ||||
|      * | ||||
|      * @param taskQuery 查询条件 | ||||
|      * @return 办理人 | ||||
|      */ | ||||
|     TaskAssigneeDTO selectDeptsByTaskAssigneeList(TaskAssigneeBody taskQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查询用户并返回任务指派的列表,支持分页 | ||||
|      * | ||||
|      * @param taskQuery 查询条件 | ||||
|      * @return 办理人 | ||||
|      */ | ||||
|     TaskAssigneeDTO selectUsersByTaskAssigneeList(TaskAssigneeBody taskQuery); | ||||
|  | ||||
| } | ||||
| @@ -1,5 +1,9 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| import org.dromara.common.core.domain.dto.UserDTO; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 通用 用户服务 | ||||
|  * | ||||
| @@ -19,8 +23,72 @@ public interface UserService { | ||||
|      * 通过用户ID查询用户账户 | ||||
|      * | ||||
|      * @param userId 用户ID | ||||
|      * @return 用户账户 | ||||
|      * @return 用户名称 | ||||
|      */ | ||||
|     String selectNicknameById(Long userId); | ||||
|  | ||||
|     /** | ||||
|      * 通过用户ID查询用户账户 | ||||
|      * | ||||
|      * @param userIds 用户ID 多个用逗号隔开 | ||||
|      * @return 用户名称 | ||||
|      */ | ||||
|     String selectNicknameByIds(String userIds); | ||||
|  | ||||
|     /** | ||||
|      * 通过用户ID查询用户手机号 | ||||
|      * | ||||
|      * @param userId 用户id | ||||
|      * @return 用户手机号 | ||||
|      */ | ||||
|     String selectPhonenumberById(Long userId); | ||||
|  | ||||
|     /** | ||||
|      * 通过用户ID查询用户邮箱 | ||||
|      * | ||||
|      * @param userId 用户id | ||||
|      * @return 用户邮箱 | ||||
|      */ | ||||
|     String selectEmailById(Long userId); | ||||
|  | ||||
|     /** | ||||
|      * 通过用户ID查询用户列表 | ||||
|      * | ||||
|      * @param userIds 用户ids | ||||
|      * @return 用户列表 | ||||
|      */ | ||||
|     List<UserDTO> selectListByIds(List<Long> userIds); | ||||
|  | ||||
|     /** | ||||
|      * 通过角色ID查询用户ID | ||||
|      * | ||||
|      * @param roleIds 角色ids | ||||
|      * @return 用户ids | ||||
|      */ | ||||
|     List<Long> selectUserIdsByRoleIds(List<Long> roleIds); | ||||
|  | ||||
|     /** | ||||
|      * 通过角色ID查询用户 | ||||
|      * | ||||
|      * @param roleIds 角色ids | ||||
|      * @return 用户 | ||||
|      */ | ||||
|     List<UserDTO> selectUsersByRoleIds(List<Long> roleIds); | ||||
|  | ||||
|     /** | ||||
|      * 通过部门ID查询用户 | ||||
|      * | ||||
|      * @param deptIds 部门ids | ||||
|      * @return 用户 | ||||
|      */ | ||||
|     List<UserDTO> selectUsersByDeptIds(List<Long> deptIds); | ||||
|  | ||||
|     /** | ||||
|      * 通过岗位ID查询用户 | ||||
|      * | ||||
|      * @param postIds 岗位ids | ||||
|      * @return 用户 | ||||
|      */ | ||||
|     List<UserDTO> selectUsersByPostIds(List<Long> postIds); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,86 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| import org.dromara.common.core.domain.dto.CompleteTaskDTO; | ||||
| import org.dromara.common.core.domain.dto.StartProcessDTO; | ||||
| import org.dromara.common.core.domain.dto.StartProcessReturnDTO; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * 通用 工作流服务 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| public interface WorkflowService { | ||||
|  | ||||
|     /** | ||||
|      * 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息 | ||||
|      * | ||||
|      * @param businessIds 业务id | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     boolean deleteInstance(List<Long> businessIds); | ||||
|  | ||||
|     /** | ||||
|      * 获取当前流程状态 | ||||
|      * | ||||
|      * @param taskId 任务id | ||||
|      * @return 状态 | ||||
|      */ | ||||
|     String getBusinessStatusByTaskId(Long taskId); | ||||
|  | ||||
|     /** | ||||
|      * 获取当前流程状态 | ||||
|      * | ||||
|      * @param businessId 业务id | ||||
|      * @return 状态 | ||||
|      */ | ||||
|     String getBusinessStatus(String businessId); | ||||
|  | ||||
|     /** | ||||
|      * 设置流程变量 | ||||
|      * | ||||
|      * @param instanceId 流程实例id | ||||
|      * @param variable   流程变量 | ||||
|      */ | ||||
|     void setVariable(Long instanceId, Map<String, Object> variable); | ||||
|  | ||||
|     /** | ||||
|      * 获取流程变量 | ||||
|      * | ||||
|      * @param instanceId 流程实例id | ||||
|      */ | ||||
|     Map<String, Object> instanceVariable(Long instanceId); | ||||
|  | ||||
|     /** | ||||
|      * 按照业务id查询流程实例id | ||||
|      * | ||||
|      * @param businessId 业务id | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     Long getInstanceIdByBusinessId(String businessId); | ||||
|  | ||||
|     /** | ||||
|      * 新增租户流程定义 | ||||
|      * | ||||
|      * @param tenantId 租户id | ||||
|      */ | ||||
|     void syncDef(String tenantId); | ||||
|  | ||||
|     /** | ||||
|      * 启动流程 | ||||
|      * | ||||
|      * @param startProcess 参数 | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     StartProcessReturnDTO startWorkFlow(StartProcessDTO startProcess); | ||||
|  | ||||
|     /** | ||||
|      * 办理任务 | ||||
|      * | ||||
|      * @param completeTask 参数 | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     boolean completeTask(CompleteTaskDTO completeTask); | ||||
| } | ||||
| @@ -1,106 +1,157 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.apache.commons.lang3.time.DateFormatUtils; | ||||
| import org.dromara.common.core.enums.FormatsType; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
|  | ||||
| import java.lang.management.ManagementFactory; | ||||
| import java.text.ParseException; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.time.LocalDate; | ||||
| import java.time.LocalDateTime; | ||||
| import java.time.LocalTime; | ||||
| import java.time.ZoneId; | ||||
| import java.time.ZonedDateTime; | ||||
| import java.time.*; | ||||
| import java.util.Date; | ||||
| import java.util.concurrent.TimeUnit; | ||||
|  | ||||
| /** | ||||
|  * 时间工具类 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class DateUtils extends org.apache.commons.lang3.time.DateUtils { | ||||
|  | ||||
|     public static final String YYYY = "yyyy"; | ||||
|  | ||||
|     public static final String YYYY_MM = "yyyy-MM"; | ||||
|  | ||||
|     public static final String YYYY_MM_DD = "yyyy-MM-dd"; | ||||
|  | ||||
|     public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; | ||||
|  | ||||
|     public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; | ||||
|  | ||||
|     private static final String[] PARSE_PATTERNS = { | ||||
|         "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", | ||||
|         "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", | ||||
|         "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; | ||||
|  | ||||
|     @Deprecated | ||||
|     private DateUtils() { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前Date型日期 | ||||
|      * 获取当前日期和时间 | ||||
|      * | ||||
|      * @return Date() 当前日期 | ||||
|      * @return 当前日期和时间的 Date 对象表示 | ||||
|      */ | ||||
|     public static Date getNowDate() { | ||||
|         return new Date(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前日期, 默认格式为yyyy-MM-dd | ||||
|      * 获取当前日期的字符串表示,格式为YYYY-MM-DD | ||||
|      * | ||||
|      * @return String | ||||
|      * @return 当前日期的字符串表示 | ||||
|      */ | ||||
|     public static String getDate() { | ||||
|         return dateTimeNow(YYYY_MM_DD); | ||||
|         return dateTimeNow(FormatsType.YYYY_MM_DD); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前日期的字符串表示,格式为yyyyMMdd | ||||
|      * | ||||
|      * @return 当前日期的字符串表示 | ||||
|      */ | ||||
|     public static String getCurrentDate() { | ||||
|         return DateFormatUtils.format(new Date(), FormatsType.YYYYMMDD.getTimeFormat()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前日期的路径格式字符串,格式为"yyyy/MM/dd" | ||||
|      * | ||||
|      * @return 当前日期的路径格式字符串 | ||||
|      */ | ||||
|     public static String datePath() { | ||||
|         Date now = new Date(); | ||||
|         return DateFormatUtils.format(now, FormatsType.YYYY_MM_DD_SLASH.getTimeFormat()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前时间的字符串表示,格式为YYYY-MM-DD HH:MM:SS | ||||
|      * | ||||
|      * @return 当前时间的字符串表示 | ||||
|      */ | ||||
|     public static String getTime() { | ||||
|         return dateTimeNow(YYYY_MM_DD_HH_MM_SS); | ||||
|         return dateTimeNow(FormatsType.YYYY_MM_DD_HH_MM_SS); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前时间的字符串表示,格式为 "HH:MM:SS" | ||||
|      * | ||||
|      * @return 当前时间的字符串表示,格式为 "HH:MM:SS" | ||||
|      */ | ||||
|     public static String getTimeWithHourMinuteSecond() { | ||||
|         return dateTimeNow(FormatsType.HH_MM_SS); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前日期和时间的字符串表示,格式为YYYYMMDDHHMMSS | ||||
|      * | ||||
|      * @return 当前日期和时间的字符串表示 | ||||
|      */ | ||||
|     public static String dateTimeNow() { | ||||
|         return dateTimeNow(YYYYMMDDHHMMSS); | ||||
|         return dateTimeNow(FormatsType.YYYYMMDDHHMMSS); | ||||
|     } | ||||
|  | ||||
|     public static String dateTimeNow(final String format) { | ||||
|     /** | ||||
|      * 获取当前日期和时间的指定格式的字符串表示 | ||||
|      * | ||||
|      * @param format 日期时间格式,例如"YYYY-MM-DD HH:MM:SS" | ||||
|      * @return 当前日期和时间的字符串表示 | ||||
|      */ | ||||
|     public static String dateTimeNow(final FormatsType format) { | ||||
|         return parseDateToStr(format, new Date()); | ||||
|     } | ||||
|  | ||||
|     public static String dateTime(final Date date) { | ||||
|         return parseDateToStr(YYYY_MM_DD, date); | ||||
|     /** | ||||
|      * 将指定日期格式化为 YYYY-MM-DD 格式的字符串 | ||||
|      * | ||||
|      * @param date 要格式化的日期对象 | ||||
|      * @return 格式化后的日期字符串 | ||||
|      */ | ||||
|     public static String formatDate(final Date date) { | ||||
|         return parseDateToStr(FormatsType.YYYY_MM_DD, date); | ||||
|     } | ||||
|  | ||||
|     public static String parseDateToStr(final String format, final Date date) { | ||||
|         return new SimpleDateFormat(format).format(date); | ||||
|     /** | ||||
|      * 将指定日期格式化为 YYYY-MM-DD HH:MM:SS 格式的字符串 | ||||
|      * | ||||
|      * @param date 要格式化的日期对象 | ||||
|      * @return 格式化后的日期时间字符串 | ||||
|      */ | ||||
|     public static String formatDateTime(final Date date) { | ||||
|         return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, date); | ||||
|     } | ||||
|  | ||||
|     public static Date dateTime(final String format, final String ts) { | ||||
|     /** | ||||
|      * 将指定日期按照指定格式进行格式化 | ||||
|      * | ||||
|      * @param format 要使用的日期时间格式,例如"YYYY-MM-DD HH:MM:SS" | ||||
|      * @param date   要格式化的日期对象 | ||||
|      * @return 格式化后的日期时间字符串 | ||||
|      */ | ||||
|     public static String parseDateToStr(final FormatsType format, final Date date) { | ||||
|         return new SimpleDateFormat(format.getTimeFormat()).format(date); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将指定格式的日期时间字符串转换为 Date 对象 | ||||
|      * | ||||
|      * @param format 要解析的日期时间格式,例如"YYYY-MM-DD HH:MM:SS" | ||||
|      * @param ts     要解析的日期时间字符串 | ||||
|      * @return 解析后的 Date 对象 | ||||
|      * @throws RuntimeException 如果解析过程中发生异常 | ||||
|      */ | ||||
|     public static Date parseDateTime(final FormatsType format, final String ts) { | ||||
|         try { | ||||
|             return new SimpleDateFormat(format).parse(ts); | ||||
|             return new SimpleDateFormat(format.getTimeFormat()).parse(ts); | ||||
|         } catch (ParseException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 日期路径 即年/月/日 如2018/08/08 | ||||
|      */ | ||||
|     public static String datePath() { | ||||
|         Date now = new Date(); | ||||
|         return DateFormatUtils.format(now, "yyyy/MM/dd"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 日期路径 即年/月/日 如20180808 | ||||
|      */ | ||||
|     public static String dateTime() { | ||||
|         Date now = new Date(); | ||||
|         return DateFormatUtils.format(now, "yyyyMMdd"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 日期型字符串转化为日期 格式 | ||||
|      * 将对象转换为日期对象 | ||||
|      * | ||||
|      * @param str 要转换的对象,通常是字符串 | ||||
|      * @return 转换后的日期对象,如果转换失败或输入为null,则返回null | ||||
|      */ | ||||
|     public static Date parseDate(Object str) { | ||||
|         if (str == null) { | ||||
| @@ -115,6 +166,8 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { | ||||
|  | ||||
|     /** | ||||
|      * 获取服务器启动时间 | ||||
|      * | ||||
|      * @return 服务器启动时间的 Date 对象表示 | ||||
|      */ | ||||
|     public static Date getServerStartDate() { | ||||
|         long time = ManagementFactory.getRuntimeMXBean().getStartTime(); | ||||
| @@ -122,35 +175,66 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 计算相差天数 | ||||
|      * 计算两个日期之间的天数差(以毫秒为单位) | ||||
|      * | ||||
|      * @param date1 第一个日期 | ||||
|      * @param date2 第二个日期 | ||||
|      * @return 两个日期之间的天数差的绝对值 | ||||
|      */ | ||||
|     public static int differentDaysByMillisecond(Date date1, Date date2) { | ||||
|         return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 计算两个时间差 | ||||
|      * 计算两个日期之间的时间差,并以天、小时和分钟的格式返回 | ||||
|      * | ||||
|      * @param endDate 结束日期 | ||||
|      * @param nowDate 当前日期 | ||||
|      * @return 表示时间差的字符串,格式为"天 小时 分钟" | ||||
|      */ | ||||
|     public static String getDatePoor(Date endDate, Date nowDate) { | ||||
|         long nd = 1000 * 24 * 60 * 60; | ||||
|         long nh = 1000 * 60 * 60; | ||||
|         long nm = 1000 * 60; | ||||
|         // long ns = 1000; | ||||
|         // 获得两个时间的毫秒时间差异 | ||||
|         long diff = endDate.getTime() - nowDate.getTime(); | ||||
|         // 计算差多少天 | ||||
|         long day = diff / nd; | ||||
|         // 计算差多少小时 | ||||
|         long hour = diff % nd / nh; | ||||
|         // 计算差多少分钟 | ||||
|         long min = diff % nd % nh / nm; | ||||
|         // 计算差多少秒//输出结果 | ||||
|         // long sec = diff % nd % nh % nm / ns; | ||||
|         return day + "天" + hour + "小时" + min + "分钟"; | ||||
|         long diffInMillis = endDate.getTime() - nowDate.getTime(); | ||||
|         long day = TimeUnit.MILLISECONDS.toDays(diffInMillis); | ||||
|         long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24; | ||||
|         long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60; | ||||
|         return String.format("%d天 %d小时 %d分钟", day, hour, min); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 增加 LocalDateTime ==> Date | ||||
|      * 计算两个时间点的差值(天、小时、分钟、秒),当值为0时不显示该单位 | ||||
|      * | ||||
|      * @param endDate 结束时间 | ||||
|      * @param nowDate 当前时间 | ||||
|      * @return 时间差字符串,格式为 "x天 x小时 x分钟 x秒",若为 0 则不显示 | ||||
|      */ | ||||
|     public static String getTimeDifference(Date endDate, Date nowDate) { | ||||
|         long diffInMillis = endDate.getTime() - nowDate.getTime(); | ||||
|         long day = TimeUnit.MILLISECONDS.toDays(diffInMillis); | ||||
|         long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24; | ||||
|         long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60; | ||||
|         long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60; | ||||
|         // 构建时间差字符串,条件是值不为0才显示 | ||||
|         StringBuilder result = new StringBuilder(); | ||||
|         if (day > 0) { | ||||
|             result.append(String.format("%d天 ", day)); | ||||
|         } | ||||
|         if (hour > 0) { | ||||
|             result.append(String.format("%d小时 ", hour)); | ||||
|         } | ||||
|         if (min > 0) { | ||||
|             result.append(String.format("%d分钟 ", min)); | ||||
|         } | ||||
|         if (sec > 0) { | ||||
|             result.append(String.format("%d秒", sec)); | ||||
|         } | ||||
|         return result.length() > 0 ? result.toString().trim() : "0秒"; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将 LocalDateTime 对象转换为 Date 对象 | ||||
|      * | ||||
|      * @param temporalAccessor 要转换的 LocalDateTime 对象 | ||||
|      * @return 转换后的 Date 对象 | ||||
|      */ | ||||
|     public static Date toDate(LocalDateTime temporalAccessor) { | ||||
|         ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); | ||||
| @@ -158,11 +242,46 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 增加 LocalDate ==> Date | ||||
|      * 将 LocalDate 对象转换为 Date 对象 | ||||
|      * | ||||
|      * @param temporalAccessor 要转换的 LocalDate 对象 | ||||
|      * @return 转换后的 Date 对象 | ||||
|      */ | ||||
|     public static Date toDate(LocalDate temporalAccessor) { | ||||
|         LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); | ||||
|         ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); | ||||
|         return Date.from(zdt.toInstant()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 校验日期范围 | ||||
|      * | ||||
|      * @param startDate 开始日期 | ||||
|      * @param endDate   结束日期 | ||||
|      * @param maxValue  最大时间跨度的限制值 | ||||
|      * @param unit      时间跨度的单位,可选择 "DAYS"、"HOURS" 或 "MINUTES" | ||||
|      */ | ||||
|     public static void validateDateRange(Date startDate, Date endDate, int maxValue, TimeUnit unit) { | ||||
|         // 校验结束日期不能早于开始日期 | ||||
|         if (endDate.before(startDate)) { | ||||
|             throw new ServiceException("结束日期不能早于开始日期"); | ||||
|         } | ||||
|  | ||||
|         // 计算时间跨度 | ||||
|         long diffInMillis = endDate.getTime() - startDate.getTime(); | ||||
|  | ||||
|         // 根据单位转换时间跨度 | ||||
|         long diff = switch (unit) { | ||||
|             case DAYS -> TimeUnit.MILLISECONDS.toDays(diffInMillis); | ||||
|             case HOURS -> TimeUnit.MILLISECONDS.toHours(diffInMillis); | ||||
|             case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffInMillis); | ||||
|             default -> throw new IllegalArgumentException("不支持的时间单位"); | ||||
|         }; | ||||
|  | ||||
|         // 校验时间跨度不超过最大限制 | ||||
|         if (diff > maxValue) { | ||||
|             throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,60 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.util.function.Function; | ||||
|  | ||||
| /** | ||||
|  * 对象工具类 | ||||
|  * | ||||
|  * @author 秋辞未寒 | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class ObjectUtils extends ObjectUtil { | ||||
|  | ||||
|     /** | ||||
|      * 如果对象不为空,则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName); | ||||
|      * | ||||
|      * @param obj 对象 | ||||
|      * @param func 获取方法 | ||||
|      * @return 对象字段 | ||||
|      */ | ||||
|     public static <T, E> E notNullGetter(T obj, Function<T, E> func) { | ||||
|         if (isNotNull(obj) && isNotNull(func)) { | ||||
|             return func.apply(obj); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果对象不为空,则获取对象中的某个字段,否则返回默认值 | ||||
|      * | ||||
|      * @param obj          对象 | ||||
|      * @param func         获取方法 | ||||
|      * @param defaultValue 默认值 | ||||
|      * @return 对象字段 | ||||
|      */ | ||||
|     public static <T, E> E notNullGetter(T obj, Function<T, E> func, E defaultValue) { | ||||
|         if (isNotNull(obj) && isNotNull(func)) { | ||||
|             return func.apply(obj); | ||||
|         } | ||||
|         return defaultValue; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果值不为空,则返回值,否则返回默认值 | ||||
|      * | ||||
|      * @param obj          对象 | ||||
|      * @param defaultValue 默认值 | ||||
|      * @return 对象字段 | ||||
|      */ | ||||
|     public static <T> T notNull(T obj, T defaultValue) { | ||||
|         if (isNotNull(obj)) { | ||||
|             return obj; | ||||
|         } | ||||
|         return defaultValue; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -25,7 +25,7 @@ import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * 客户端工具类 | ||||
|  * 客户端工具类,提供获取请求参数、响应处理、头部信息等常用操作 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @@ -33,52 +33,73 @@ import java.util.Map; | ||||
| public class ServletUtils extends JakartaServletUtil { | ||||
|  | ||||
|     /** | ||||
|      * 获取String参数 | ||||
|      * 获取指定名称的 String 类型的请求参数 | ||||
|      * | ||||
|      * @param name 参数名 | ||||
|      * @return 参数值 | ||||
|      */ | ||||
|     public static String getParameter(String name) { | ||||
|         return getRequest().getParameter(name); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取String参数 | ||||
|      * 获取指定名称的 String 类型的请求参数,若参数不存在,则返回默认值 | ||||
|      * | ||||
|      * @param name         参数名 | ||||
|      * @param defaultValue 默认值 | ||||
|      * @return 参数值或默认值 | ||||
|      */ | ||||
|     public static String getParameter(String name, String defaultValue) { | ||||
|         return Convert.toStr(getRequest().getParameter(name), defaultValue); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取Integer参数 | ||||
|      * 获取指定名称的 Integer 类型的请求参数 | ||||
|      * | ||||
|      * @param name 参数名 | ||||
|      * @return 参数值 | ||||
|      */ | ||||
|     public static Integer getParameterToInt(String name) { | ||||
|         return Convert.toInt(getRequest().getParameter(name)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取Integer参数 | ||||
|      * 获取指定名称的 Integer 类型的请求参数,若参数不存在,则返回默认值 | ||||
|      * | ||||
|      * @param name         参数名 | ||||
|      * @param defaultValue 默认值 | ||||
|      * @return 参数值或默认值 | ||||
|      */ | ||||
|     public static Integer getParameterToInt(String name, Integer defaultValue) { | ||||
|         return Convert.toInt(getRequest().getParameter(name), defaultValue); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取Boolean参数 | ||||
|      * 获取指定名称的 Boolean 类型的请求参数 | ||||
|      * | ||||
|      * @param name 参数名 | ||||
|      * @return 参数值 | ||||
|      */ | ||||
|     public static Boolean getParameterToBool(String name) { | ||||
|         return Convert.toBool(getRequest().getParameter(name)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取Boolean参数 | ||||
|      * 获取指定名称的 Boolean 类型的请求参数,若参数不存在,则返回默认值 | ||||
|      * | ||||
|      * @param name         参数名 | ||||
|      * @param defaultValue 默认值 | ||||
|      * @return 参数值或默认值 | ||||
|      */ | ||||
|     public static Boolean getParameterToBool(String name, Boolean defaultValue) { | ||||
|         return Convert.toBool(getRequest().getParameter(name), defaultValue); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获得所有请求参数 | ||||
|      * 获取所有请求参数(以 Map 的形式返回) | ||||
|      * | ||||
|      * @param request 请求对象{@link ServletRequest} | ||||
|      * @return Map | ||||
|      * @return 请求参数的 Map,键为参数名,值为参数值数组 | ||||
|      */ | ||||
|     public static Map<String, String[]> getParams(ServletRequest request) { | ||||
|         final Map<String, String[]> map = request.getParameterMap(); | ||||
| @@ -86,10 +107,10 @@ public class ServletUtils extends JakartaServletUtil { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获得所有请求参数 | ||||
|      * 获取所有请求参数(以 Map 的形式返回,值为字符串形式的拼接) | ||||
|      * | ||||
|      * @param request 请求对象{@link ServletRequest} | ||||
|      * @return Map | ||||
|      * @return 请求参数的 Map,键为参数名,值为拼接后的字符串 | ||||
|      */ | ||||
|     public static Map<String, String> getParamMap(ServletRequest request) { | ||||
|         Map<String, String> params = new HashMap<>(); | ||||
| @@ -100,7 +121,9 @@ public class ServletUtils extends JakartaServletUtil { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取request | ||||
|      * 获取当前 HTTP 请求对象 | ||||
|      * | ||||
|      * @return 当前 HTTP 请求对象 | ||||
|      */ | ||||
|     public static HttpServletRequest getRequest() { | ||||
|         try { | ||||
| @@ -111,7 +134,9 @@ public class ServletUtils extends JakartaServletUtil { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取response | ||||
|      * 获取当前 HTTP 响应对象 | ||||
|      * | ||||
|      * @return 当前 HTTP 响应对象 | ||||
|      */ | ||||
|     public static HttpServletResponse getResponse() { | ||||
|         try { | ||||
| @@ -122,12 +147,25 @@ public class ServletUtils extends JakartaServletUtil { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取session | ||||
|      * 获取当前请求的 HttpSession 对象 | ||||
|      * <p> | ||||
|      * 如果当前请求已经关联了一个会话(即已经存在有效的 session ID), | ||||
|      * 则返回该会话对象;如果没有关联会话,则会创建一个新的会话对象并返回。 | ||||
|      * <p> | ||||
|      * HttpSession 用于存储会话级别的数据,如用户登录信息、购物车内容等, | ||||
|      * 可以在多个请求之间共享会话数据 | ||||
|      * | ||||
|      * @return 当前请求的 HttpSession 对象 | ||||
|      */ | ||||
|     public static HttpSession getSession() { | ||||
|         return getRequest().getSession(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前请求的请求属性 | ||||
|      * | ||||
|      * @return {@link ServletRequestAttributes} 请求属性对象 | ||||
|      */ | ||||
|     public static ServletRequestAttributes getRequestAttributes() { | ||||
|         try { | ||||
|             RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); | ||||
| @@ -137,6 +175,13 @@ public class ServletUtils extends JakartaServletUtil { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取指定请求头的值,如果头部为空则返回空字符串 | ||||
|      * | ||||
|      * @param request 请求对象 | ||||
|      * @param name    头部名称 | ||||
|      * @return 头部值 | ||||
|      */ | ||||
|     public static String getHeader(HttpServletRequest request, String name) { | ||||
|         String value = request.getHeader(name); | ||||
|         if (StringUtils.isEmpty(value)) { | ||||
| @@ -145,6 +190,12 @@ public class ServletUtils extends JakartaServletUtil { | ||||
|         return urlDecode(value); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取所有请求头的 Map,键为头部名称,值为头部值 | ||||
|      * | ||||
|      * @param request 请求对象 | ||||
|      * @return 请求头的 Map | ||||
|      */ | ||||
|     public static Map<String, String> getHeaders(HttpServletRequest request) { | ||||
|         Map<String, String> map = new LinkedCaseInsensitiveMap<>(); | ||||
|         Enumeration<String> enumeration = request.getHeaderNames(); | ||||
| @@ -159,7 +210,7 @@ public class ServletUtils extends JakartaServletUtil { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将字符串渲染到客户端 | ||||
|      * 将字符串渲染到客户端(以 JSON 格式返回) | ||||
|      * | ||||
|      * @param response 渲染对象 | ||||
|      * @param string   待渲染的字符串 | ||||
| @@ -176,37 +227,47 @@ public class ServletUtils extends JakartaServletUtil { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 是否是Ajax异步请求 | ||||
|      * 判断当前请求是否为 Ajax 异步请求 | ||||
|      * | ||||
|      * @param request | ||||
|      * @param request 请求对象 | ||||
|      * @return 是否为 Ajax 请求 | ||||
|      */ | ||||
|     public static boolean isAjaxRequest(HttpServletRequest request) { | ||||
|  | ||||
|         // 判断 Accept 头部是否包含 application/json | ||||
|         String accept = request.getHeader("accept"); | ||||
|         if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // 判断 X-Requested-With 头部是否包含 XMLHttpRequest | ||||
|         String xRequestedWith = request.getHeader("X-Requested-With"); | ||||
|         if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // 判断 URI 后缀是否为 .json 或 .xml | ||||
|         String uri = request.getRequestURI(); | ||||
|         if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // 判断请求参数 __ajax 是否为 json 或 xml | ||||
|         String ajax = request.getParameter("__ajax"); | ||||
|         return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取客户端 IP 地址 | ||||
|      * | ||||
|      * @return 客户端 IP 地址 | ||||
|      */ | ||||
|     public static String getClientIP() { | ||||
|         return getClientIP(getRequest()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 内容编码 | ||||
|      * 对内容进行 URL 编码 | ||||
|      * | ||||
|      * @param str 内容 | ||||
|      * @return 编码后的内容 | ||||
| @@ -216,7 +277,7 @@ public class ServletUtils extends JakartaServletUtil { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 内容解码 | ||||
|      * 对内容进行 URL 解码 | ||||
|      * | ||||
|      * @param str 内容 | ||||
|      * @return 解码后的内容 | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import org.springframework.aop.framework.AopContext; | ||||
| import org.springframework.beans.factory.NoSuchBeanDefinitionException; | ||||
| import org.springframework.boot.autoconfigure.thread.Threading; | ||||
| import org.springframework.context.ApplicationContext; | ||||
| import org.springframework.core.env.Environment; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
| @@ -48,7 +49,7 @@ public final class SpringUtils extends SpringUtil { | ||||
|      */ | ||||
|     @SuppressWarnings("unchecked") | ||||
|     public static <T> T getAopProxy(T invoker) { | ||||
|         return (T) AopContext.currentProxy(); | ||||
|         return (T) getBean(invoker.getClass()); | ||||
|     } | ||||
|  | ||||
|  | ||||
| @@ -59,4 +60,8 @@ public final class SpringUtils extends SpringUtil { | ||||
|         return getApplicationContext(); | ||||
|     } | ||||
|  | ||||
|     public static boolean isVirtual() { | ||||
|         return Threading.VIRTUAL.isActive(getBean(Environment.class)); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.util.*; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Function; | ||||
| import java.util.function.Predicate; | ||||
| import java.util.stream.Collectors; | ||||
| @@ -34,6 +35,34 @@ public class StreamUtils { | ||||
|         return collection.stream().filter(function).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 找到流中满足条件的第一个元素 | ||||
|      * | ||||
|      * @param collection 需要查询的集合 | ||||
|      * @param function   过滤方法 | ||||
|      * @return 找到符合条件的第一个元素,没有则返回null | ||||
|      */ | ||||
|     public static <E> E findFirst(Collection<E> collection, Predicate<E> function) { | ||||
|         if (CollUtil.isEmpty(collection)) { | ||||
|             return null; | ||||
|         } | ||||
|         return collection.stream().filter(function).findFirst().orElse(null); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 找到流中任意一个满足条件的元素 | ||||
|      * | ||||
|      * @param collection 需要查询的集合 | ||||
|      * @param function   过滤方法 | ||||
|      * @return 找到符合条件的任意一个元素,没有则返回null | ||||
|      */ | ||||
|     public static <E> Optional<E> findAny(Collection<E> collection, Predicate<E> function) { | ||||
|         if (CollUtil.isEmpty(collection)) { | ||||
|             return Optional.empty(); | ||||
|         } | ||||
|         return collection.stream().filter(function).findAny(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将collection拼接 | ||||
|      * | ||||
|   | ||||
| @@ -4,8 +4,6 @@ import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.convert.Convert; | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.springframework.util.AntPathMatcher; | ||||
|  | ||||
| import java.util.*; | ||||
| @@ -17,11 +15,16 @@ import java.util.stream.Collectors; | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class StringUtils extends org.apache.commons.lang3.StringUtils { | ||||
|  | ||||
|     public static final String SEPARATOR = ","; | ||||
|  | ||||
|     public static final String SLASH = "/"; | ||||
|  | ||||
|     @Deprecated | ||||
|     private StringUtils() { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取参数不为空值 | ||||
|      * | ||||
| @@ -315,7 +318,25 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { | ||||
|             .stream() | ||||
|             .filter(Objects::nonNull) | ||||
|             .map(mapper) | ||||
|             .filter(Objects::nonNull) | ||||
|             .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 不区分大小写检查 CharSequence 是否以指定的前缀开头。 | ||||
|      * | ||||
|      * @param str     要检查的 CharSequence 可能为 null | ||||
|      * @param prefixs 要查找的前缀可能为 null | ||||
|      * @return 是否包含 | ||||
|      */ | ||||
|     public static boolean startWithAnyIgnoreCase(CharSequence str, CharSequence... prefixs) { | ||||
|         // 判断是否是以指定字符串开头 | ||||
|         for (CharSequence prefix : prefixs) { | ||||
|             if (StringUtils.startsWithIgnoreCase(str, prefix)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -14,18 +14,6 @@ import java.util.concurrent.*; | ||||
| @Slf4j | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class Threads { | ||||
|  | ||||
|     /** | ||||
|      * sleep等待,单位为毫秒 | ||||
|      */ | ||||
|     public static void sleep(long milliseconds) { | ||||
|         try { | ||||
|             Thread.sleep(milliseconds); | ||||
|         } catch (InterruptedException e) { | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 停止线程池 | ||||
|      * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. | ||||
|   | ||||
| @@ -5,11 +5,13 @@ import cn.hutool.core.lang.tree.Tree; | ||||
| import cn.hutool.core.lang.tree.TreeNodeConfig; | ||||
| import cn.hutool.core.lang.tree.TreeUtil; | ||||
| import cn.hutool.core.lang.tree.parser.NodeParser; | ||||
| import org.dromara.common.core.utils.reflect.ReflectUtils; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.dromara.common.core.utils.reflect.ReflectUtils; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| import java.util.stream.Stream; | ||||
|  | ||||
| /** | ||||
|  * 扩展 hutool TreeUtil 封装系统树构建 | ||||
| @@ -24,12 +26,71 @@ public class TreeBuildUtils extends TreeUtil { | ||||
|      */ | ||||
|     public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label"); | ||||
|  | ||||
|     /** | ||||
|      * 构建树形结构 | ||||
|      * | ||||
|      * @param <T>        输入节点的类型 | ||||
|      * @param <K>        节点ID的类型 | ||||
|      * @param list       节点列表,其中包含了要构建树形结构的所有节点 | ||||
|      * @param nodeParser 解析器,用于将输入节点转换为树节点 | ||||
|      * @return 构建好的树形结构列表 | ||||
|      */ | ||||
|     public static <T, K> List<Tree<K>> build(List<T> list, NodeParser<T, K> nodeParser) { | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return null; | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         K k = ReflectUtils.invokeGetter(list.get(0), "parentId"); | ||||
|         return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 构建树形结构 | ||||
|      * | ||||
|      * @param <T>        输入节点的类型 | ||||
|      * @param <K>        节点ID的类型 | ||||
|      * @param parentId   顶级节点 | ||||
|      * @param list       节点列表,其中包含了要构建树形结构的所有节点 | ||||
|      * @param nodeParser 解析器,用于将输入节点转换为树节点 | ||||
|      * @return 构建好的树形结构列表 | ||||
|      */ | ||||
|     public static <T, K> List<Tree<K>> build(List<T> list, K parentId, NodeParser<T, K> nodeParser) { | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取节点列表中所有节点的叶子节点 | ||||
|      * | ||||
|      * @param <K>   节点ID的类型 | ||||
|      * @param nodes 节点列表 | ||||
|      * @return 包含所有叶子节点的列表 | ||||
|      */ | ||||
|     public static <K> List<Tree<K>> getLeafNodes(List<Tree<K>> nodes) { | ||||
|         if (CollUtil.isEmpty(nodes)) { | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         return nodes.stream() | ||||
|             .flatMap(TreeBuildUtils::extractLeafNodes) | ||||
|             .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取指定节点下的所有叶子节点 | ||||
|      * | ||||
|      * @param <K>  节点ID的类型 | ||||
|      * @param node 要查找叶子节点的根节点 | ||||
|      * @return 包含所有叶子节点的列表 | ||||
|      */ | ||||
|     private static <K> Stream<Tree<K>> extractLeafNodes(Tree<K> node) { | ||||
|         if (!node.hasChild()) { | ||||
|             return Stream.of(node); | ||||
|         } else { | ||||
|             // 递归调用,获取所有子节点的叶子节点 | ||||
|             return node.getChildren().stream() | ||||
|                 .flatMap(TreeBuildUtils::extractLeafNodes); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import jakarta.validation.ConstraintViolation; | ||||
| import jakarta.validation.ConstraintViolationException; | ||||
| import jakarta.validation.Validator; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.util.Set; | ||||
|  | ||||
| /** | ||||
| @@ -18,6 +18,13 @@ public class ValidatorUtils { | ||||
|  | ||||
|     private static final Validator VALID = SpringUtils.getBean(Validator.class); | ||||
|  | ||||
|     /** | ||||
|      * 对给定对象进行参数校验,并根据指定的校验组进行校验 | ||||
|      * | ||||
|      * @param object 要进行校验的对象 | ||||
|      * @param groups 校验组 | ||||
|      * @throws ConstraintViolationException 如果校验不通过,则抛出参数校验异常 | ||||
|      */ | ||||
|     public static <T> void validate(T object, Class<?>... groups) { | ||||
|         Set<ConstraintViolation<T>> validate = VALID.validate(object, groups); | ||||
|         if (!validate.isEmpty()) { | ||||
|   | ||||
| @@ -0,0 +1,31 @@ | ||||
| package org.dromara.common.core.utils.regex; | ||||
|  | ||||
|  | ||||
| import cn.hutool.core.util.ReUtil; | ||||
| import org.dromara.common.core.constant.RegexConstants; | ||||
|  | ||||
| /** | ||||
|  * 正则相关工具类 | ||||
|  * | ||||
|  * @author Feng | ||||
|  */ | ||||
| public final class RegexUtils extends ReUtil { | ||||
|  | ||||
|     /** | ||||
|      * 从输入字符串中提取匹配的部分,如果没有匹配则返回默认值 | ||||
|      * | ||||
|      * @param input        要提取的输入字符串 | ||||
|      * @param regex        用于匹配的正则表达式,可以使用 {@link RegexConstants} 中定义的常量 | ||||
|      * @param defaultInput 如果没有匹配时返回的默认值 | ||||
|      * @return 如果找到匹配的部分,则返回匹配的部分,否则返回默认值 | ||||
|      */ | ||||
|     public static String extractFromString(String input, String regex, String defaultInput) { | ||||
|         try { | ||||
|             String str = ReUtil.get(regex, input, 1); | ||||
|             return str == null ? defaultInput : str; | ||||
|         } catch (Exception e) { | ||||
|             return defaultInput; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,105 @@ | ||||
| package org.dromara.common.core.utils.regex; | ||||
|  | ||||
| import cn.hutool.core.exceptions.ValidateException; | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import org.dromara.common.core.factory.RegexPatternPoolFactory; | ||||
|  | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| /** | ||||
|  * 正则字段校验器 | ||||
|  * 主要验证字段非空、是否为满足指定格式等 | ||||
|  * | ||||
|  * @author Feng | ||||
|  */ | ||||
| public class RegexValidator extends Validator { | ||||
|  | ||||
|     /** | ||||
|      * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线) | ||||
|      */ | ||||
|     public static final Pattern DICTIONARY_TYPE = RegexPatternPoolFactory.DICTIONARY_TYPE; | ||||
|  | ||||
|     /** | ||||
|      * 身份证号码(后6位) | ||||
|      */ | ||||
|     public static final Pattern ID_CARD_LAST_6 = RegexPatternPoolFactory.ID_CARD_LAST_6; | ||||
|  | ||||
|     /** | ||||
|      * QQ号码 | ||||
|      */ | ||||
|     public static final Pattern QQ_NUMBER = RegexPatternPoolFactory.QQ_NUMBER; | ||||
|  | ||||
|     /** | ||||
|      * 邮政编码 | ||||
|      */ | ||||
|     public static final Pattern POSTAL_CODE = RegexPatternPoolFactory.POSTAL_CODE; | ||||
|  | ||||
|     /** | ||||
|      * 注册账号 | ||||
|      */ | ||||
|     public static final Pattern ACCOUNT = RegexPatternPoolFactory.ACCOUNT; | ||||
|  | ||||
|     /** | ||||
|      * 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符 | ||||
|      */ | ||||
|     public static final Pattern PASSWORD = RegexPatternPoolFactory.PASSWORD; | ||||
|  | ||||
|     /** | ||||
|      * 通用状态(0表示正常,1表示停用) | ||||
|      */ | ||||
|     public static final Pattern STATUS = RegexPatternPoolFactory.STATUS; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 检查输入的账号是否匹配预定义的规则 | ||||
|      * | ||||
|      * @param value 要验证的账号 | ||||
|      * @return 如果账号符合规则,返回 true;否则,返回 false。 | ||||
|      */ | ||||
|     public static boolean isAccount(CharSequence value) { | ||||
|         return isMatchRegex(ACCOUNT, value); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 验证输入的账号是否符合规则,如果不符合,则抛出 ValidateException 异常 | ||||
|      * | ||||
|      * @param value    要验证的账号 | ||||
|      * @param errorMsg 验证失败时抛出的异常消息 | ||||
|      * @param <T>      CharSequence 的子类型 | ||||
|      * @return 如果验证通过,返回输入的账号 | ||||
|      * @throws ValidateException 如果验证失败 | ||||
|      */ | ||||
|     public static <T extends CharSequence> T validateAccount(T value, String errorMsg) throws ValidateException { | ||||
|         if (!isAccount(value)) { | ||||
|             throw new ValidateException(errorMsg); | ||||
|         } | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 检查输入的状态是否匹配预定义的规则 | ||||
|      * | ||||
|      * @param value 要验证的状态 | ||||
|      * @return 如果状态符合规则,返回 true;否则,返回 false。 | ||||
|      */ | ||||
|     public static boolean isStatus(CharSequence value) { | ||||
|         return isMatchRegex(STATUS, value); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 验证输入的状态是否符合规则,如果不符合,则抛出 ValidateException 异常 | ||||
|      * | ||||
|      * @param value    要验证的状态 | ||||
|      * @param errorMsg 验证失败时抛出的异常消息 | ||||
|      * @param <T>      CharSequence 的子类型 | ||||
|      * @return 如果验证通过,返回输入的状态 | ||||
|      * @throws ValidateException 如果验证失败 | ||||
|      */ | ||||
|     public static <T extends CharSequence> T validateStatus(T value, String errorMsg) throws ValidateException { | ||||
|         if (!isStatus(value)) { | ||||
|             throw new ValidateException(errorMsg); | ||||
|         } | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -15,7 +15,7 @@ public class SqlUtil { | ||||
|     /** | ||||
|      * 定义常用的 sql关键字 | ||||
|      */ | ||||
|     public static final String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare "; | ||||
|     public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()"; | ||||
|  | ||||
|     /** | ||||
|      * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) | ||||
|   | ||||
| @@ -0,0 +1,48 @@ | ||||
| package org.dromara.common.core.validate.enumd; | ||||
|  | ||||
| import jakarta.validation.Constraint; | ||||
| import jakarta.validation.Payload; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| import static java.lang.annotation.ElementType.*; | ||||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||||
|  | ||||
| /** | ||||
|  * 自定义枚举校验 | ||||
|  * | ||||
|  * @author 秋辞未寒 | ||||
|  * @date 2024-12-09 | ||||
|  */ | ||||
| @Documented | ||||
| @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) | ||||
| @Retention(RUNTIME) | ||||
| @Repeatable(EnumPattern.List.class) // 允许在同一元素上多次使用该注解 | ||||
| @Constraint(validatedBy = {EnumPatternValidator.class}) | ||||
| public @interface EnumPattern { | ||||
|  | ||||
|     /** | ||||
|      * 需要校验的枚举类型 | ||||
|      */ | ||||
|     Class<? extends Enum<?>> type(); | ||||
|  | ||||
|     /** | ||||
|      * 枚举类型校验值字段名称 | ||||
|      * 需确保该字段实现了 getter 方法 | ||||
|      */ | ||||
|     String fieldName(); | ||||
|  | ||||
|     String message() default "输入值不在枚举范围内"; | ||||
|  | ||||
|     Class<?>[] groups() default {}; | ||||
|  | ||||
|     Class<? extends Payload>[] payload() default {}; | ||||
|  | ||||
|     @Documented | ||||
|     @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) | ||||
|     @Retention(RUNTIME) | ||||
|     @interface List { | ||||
|         EnumPattern[] value(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,37 @@ | ||||
| package org.dromara.common.core.validate.enumd; | ||||
|  | ||||
| import jakarta.validation.ConstraintValidator; | ||||
| import jakarta.validation.ConstraintValidatorContext; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import org.dromara.common.core.utils.reflect.ReflectUtils; | ||||
|  | ||||
| /** | ||||
|  * 自定义枚举校验注解实现 | ||||
|  * | ||||
|  * @author 秋辞未寒 | ||||
|  * @date 2024-12-09 | ||||
|  */ | ||||
| public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> { | ||||
|  | ||||
|     private EnumPattern annotation;; | ||||
|  | ||||
|     @Override | ||||
|     public void initialize(EnumPattern annotation) { | ||||
|         ConstraintValidator.super.initialize(annotation); | ||||
|         this.annotation = annotation; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { | ||||
|         if (StringUtils.isNotBlank(value)) { | ||||
|             String fieldName = annotation.fieldName(); | ||||
|             for (Object e : annotation.type().getEnumConstants()) { | ||||
|                 if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,6 +1,5 @@ | ||||
| org.dromara.common.core.config.ApplicationConfig | ||||
| org.dromara.common.core.config.AsyncConfig | ||||
| org.dromara.common.core.config.RuoYiConfig | ||||
| org.dromara.common.core.config.ThreadPoolConfig | ||||
| org.dromara.common.core.config.ValidatorConfig | ||||
| org.dromara.common.core.utils.SpringUtils | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import io.swagger.v3.oas.models.Paths; | ||||
| import io.swagger.v3.oas.models.tags.Tag; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.dromara.common.core.utils.StreamUtils; | ||||
| import org.springdoc.core.customizers.OpenApiBuilderCustomizer; | ||||
| import org.springdoc.core.customizers.ServerBaseUrlCustomizer; | ||||
| import org.springdoc.core.properties.SpringDocConfigProperties; | ||||
| @@ -230,7 +231,7 @@ public class OpenApiHandler extends OpenAPIService { | ||||
|             .flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet()); | ||||
|         methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class)); | ||||
|         if (!CollectionUtils.isEmpty(methodTags)) { | ||||
|             tagsStr.addAll(methodTags.stream().map(tag -> propertyResolverUtils.resolve(tag.name(), locale)).collect(Collectors.toSet())); | ||||
|             tagsStr.addAll(StreamUtils.toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale))); | ||||
|             List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags); | ||||
|             addTags(allTags, tags, locale); | ||||
|         } | ||||
|   | ||||
| @@ -22,11 +22,6 @@ | ||||
|             <artifactId>ruoyi-common-core</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <dependency> | ||||
|             <groupId>org.mybatis.spring.boot</groupId> | ||||
|             <artifactId>mybatis-spring-boot-starter</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <dependency> | ||||
|             <groupId>org.bouncycastle</groupId> | ||||
|             <artifactId>bcprov-jdk15to18</artifactId> | ||||
| @@ -42,6 +37,18 @@ | ||||
|             <artifactId>spring-webmvc</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <dependency> | ||||
|             <groupId>com.baomidou</groupId> | ||||
|             <artifactId>mybatis-plus-spring-boot3-starter</artifactId> | ||||
|             <optional>true</optional> | ||||
|             <exclusions> | ||||
|                 <exclusion> | ||||
|                     <groupId>org.mybatis</groupId> | ||||
|                     <artifactId>mybatis-spring</artifactId> | ||||
|                 </exclusion> | ||||
|             </exclusions> | ||||
|         </dependency> | ||||
|  | ||||
|     </dependencies> | ||||
|  | ||||
| </project> | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| package org.dromara.common.encrypt.config; | ||||
|  | ||||
| import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; | ||||
| import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.dromara.common.encrypt.core.EncryptorManager; | ||||
| import org.dromara.common.encrypt.interceptor.MybatisDecryptInterceptor; | ||||
| import org.dromara.common.encrypt.interceptor.MybatisEncryptInterceptor; | ||||
| @@ -16,17 +19,18 @@ import org.springframework.context.annotation.Bean; | ||||
|  * @author 老马 | ||||
|  * @version 4.6.0 | ||||
|  */ | ||||
| @AutoConfiguration | ||||
| @AutoConfiguration(after = MybatisPlusAutoConfiguration.class) | ||||
| @EnableConfigurationProperties(EncryptorProperties.class) | ||||
| @ConditionalOnProperty(value = "mybatis-encryptor.enable", havingValue = "true") | ||||
| @Slf4j | ||||
| public class EncryptorAutoConfiguration { | ||||
|  | ||||
|     @Autowired | ||||
|     private EncryptorProperties properties; | ||||
|  | ||||
|     @Bean | ||||
|     public EncryptorManager encryptorManager() { | ||||
|         return new EncryptorManager(); | ||||
|     public EncryptorManager encryptorManager(MybatisPlusProperties mybatisPlusProperties) { | ||||
|         return new EncryptorManager(mybatisPlusProperties.getTypeAliasesPackage()); | ||||
|     } | ||||
|  | ||||
|     @Bean | ||||
| @@ -38,4 +42,8 @@ public class EncryptorAutoConfiguration { | ||||
|     public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptorManager encryptorManager) { | ||||
|         return new MybatisDecryptInterceptor(encryptorManager, properties); | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,20 @@ | ||||
| package org.dromara.common.encrypt.core; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.util.ReflectUtil; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.ibatis.io.Resources; | ||||
| import org.dromara.common.core.utils.ObjectUtils; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import org.dromara.common.encrypt.annotation.EncryptField; | ||||
| import org.springframework.context.ConfigurableApplicationContext; | ||||
| import org.springframework.core.io.Resource; | ||||
| import org.springframework.core.io.support.PathMatchingResourcePatternResolver; | ||||
| import org.springframework.core.io.support.ResourcePatternResolver; | ||||
| import org.springframework.core.type.ClassMetadata; | ||||
| import org.springframework.core.type.classreading.CachingMetadataReaderFactory; | ||||
| import org.springframework.util.ClassUtils; | ||||
|  | ||||
| import java.lang.reflect.Field; | ||||
| import java.util.Arrays; | ||||
| @@ -19,37 +31,34 @@ import java.util.stream.Collectors; | ||||
|  * @version 4.6.0 | ||||
|  */ | ||||
| @Slf4j | ||||
| @NoArgsConstructor | ||||
| public class EncryptorManager { | ||||
|  | ||||
|     /** | ||||
|      * 缓存加密器 | ||||
|      */ | ||||
|     Map<EncryptContext, IEncryptor> encryptorMap = new ConcurrentHashMap<>(); | ||||
|     Map<Integer, IEncryptor> encryptorMap = new ConcurrentHashMap<>(); | ||||
|  | ||||
|     /** | ||||
|      * 类加密字段缓存 | ||||
|      */ | ||||
|     Map<Class<?>, Set<Field>> fieldCache = new ConcurrentHashMap<>(); | ||||
|  | ||||
|     /** | ||||
|      * 构造方法传入类加密字段缓存 | ||||
|      * | ||||
|      * @param typeAliasesPackage 实体类包 | ||||
|      */ | ||||
|     public EncryptorManager(String typeAliasesPackage) { | ||||
|         scanEncryptClasses(typeAliasesPackage); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 获取类加密字段缓存 | ||||
|      */ | ||||
|     public Set<Field> getFieldCache(Class<?> sourceClazz) { | ||||
|         return fieldCache.computeIfAbsent(sourceClazz, clazz -> { | ||||
|             Set<Field> fieldSet = new HashSet<>(); | ||||
|             while (clazz != null) { | ||||
|                 Field[] fields = clazz.getDeclaredFields(); | ||||
|                 fieldSet.addAll(Arrays.asList(fields)); | ||||
|                 clazz = clazz.getSuperclass(); | ||||
|             } | ||||
|             fieldSet = fieldSet.stream().filter(field -> | ||||
|                     field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class) | ||||
|                 .collect(Collectors.toSet()); | ||||
|             for (Field field : fieldSet) { | ||||
|                 field.setAccessible(true); | ||||
|             } | ||||
|             return fieldSet; | ||||
|         }); | ||||
|         return ObjectUtils.notNullGetter(fieldCache, f -> f.get(sourceClazz)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -58,11 +67,12 @@ public class EncryptorManager { | ||||
|      * @param encryptContext 加密执行者需要的相关配置参数 | ||||
|      */ | ||||
|     public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) { | ||||
|         if (encryptorMap.containsKey(encryptContext)) { | ||||
|             return encryptorMap.get(encryptContext); | ||||
|         int key = encryptContext.hashCode(); | ||||
|         if (encryptorMap.containsKey(key)) { | ||||
|             return encryptorMap.get(key); | ||||
|         } | ||||
|         IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext); | ||||
|         encryptorMap.put(encryptContext, encryptor); | ||||
|         encryptorMap.put(key, encryptor); | ||||
|         return encryptor; | ||||
|     } | ||||
|  | ||||
| @@ -72,7 +82,7 @@ public class EncryptorManager { | ||||
|      * @param encryptContext 加密执行者需要的相关配置参数 | ||||
|      */ | ||||
|     public void removeEncryptor(EncryptContext encryptContext) { | ||||
|         this.encryptorMap.remove(encryptContext); | ||||
|         this.encryptorMap.remove(encryptContext.hashCode()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -97,4 +107,53 @@ public class EncryptorManager { | ||||
|         return encryptor.decrypt(value); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 通过 typeAliasesPackage 设置的扫描包 扫描缓存实体 | ||||
|      */ | ||||
|     private void scanEncryptClasses(String typeAliasesPackage) { | ||||
|         PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); | ||||
|         CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); | ||||
|         String[] packagePatternArray = StringUtils.splitPreserveAllTokens(typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); | ||||
|         String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX; | ||||
|         try { | ||||
|             for (String packagePattern : packagePatternArray) { | ||||
|                 String path = ClassUtils.convertClassNameToResourcePath(packagePattern); | ||||
|                 Resource[] resources = resolver.getResources(classpath + path + "/*.class"); | ||||
|                 for (Resource resource : resources) { | ||||
|                     ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata(); | ||||
|                     Class<?> clazz = Resources.classForName(classMetadata.getClassName()); | ||||
|                     Set<Field> encryptFieldSet = getEncryptFieldSetFromClazz(clazz); | ||||
|                     if (CollUtil.isNotEmpty(encryptFieldSet)) { | ||||
|                         fieldCache.put(clazz, encryptFieldSet); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             log.error("初始化数据安全缓存时出错:{}", e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获得一个类的加密字段集合 | ||||
|      */ | ||||
|     private Set<Field> getEncryptFieldSetFromClazz(Class<?> clazz) { | ||||
|         Set<Field> fieldSet = new HashSet<>(); | ||||
|         // 判断clazz如果是接口,内部类,匿名类就直接返回 | ||||
|         if (clazz.isInterface() || clazz.isMemberClass() || clazz.isAnonymousClass()) { | ||||
|             return fieldSet; | ||||
|         } | ||||
|         while (clazz != null) { | ||||
|             Field[] fields = clazz.getDeclaredFields(); | ||||
|             fieldSet.addAll(Arrays.asList(fields)); | ||||
|             clazz = clazz.getSuperclass(); | ||||
|         } | ||||
|         fieldSet = fieldSet.stream().filter(field -> | ||||
|                 field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class) | ||||
|             .collect(Collectors.toSet()); | ||||
|         for (Field field : fieldSet) { | ||||
|             field.setAccessible(true); | ||||
|         } | ||||
|         return fieldSet; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -11,7 +11,6 @@ import org.dromara.common.core.utils.StringUtils; | ||||
| import org.dromara.common.encrypt.annotation.ApiEncrypt; | ||||
| import org.dromara.common.encrypt.properties.ApiDecryptProperties; | ||||
| import org.springframework.http.HttpMethod; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.web.method.HandlerMethod; | ||||
| import org.springframework.web.servlet.HandlerExceptionResolver; | ||||
| import org.springframework.web.servlet.HandlerExecutionChain; | ||||
| @@ -36,47 +35,43 @@ public class CryptoFilter implements Filter { | ||||
|     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { | ||||
|         HttpServletRequest servletRequest = (HttpServletRequest) request; | ||||
|         HttpServletResponse servletResponse = (HttpServletResponse) response; | ||||
|  | ||||
|         boolean encryptFlag = false; | ||||
|         // 获取加密注解 | ||||
|         ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest); | ||||
|         boolean responseFlag = apiEncrypt != null && apiEncrypt.response(); | ||||
|         ServletRequest requestWrapper = null; | ||||
|         ServletResponse responseWrapper = null; | ||||
|         EncryptResponseBodyWrapper responseBodyWrapper = null; | ||||
|  | ||||
|         // 是否为 json 请求 | ||||
|         if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { | ||||
|             // 是否为 put 或者 post 请求 | ||||
|             if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) { | ||||
|                 // 是否存在加密标头 | ||||
|                 String headerValue = servletRequest.getHeader(properties.getHeaderFlag()); | ||||
|                 if (StringUtils.isNotBlank(headerValue)) { | ||||
|                     // 请求解密 | ||||
|                     requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag()); | ||||
|                     // 获取加密注解 | ||||
|                     ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest); | ||||
|                     if (ObjectUtil.isNotNull(apiEncrypt)) { | ||||
|                         // 响应加密标志 | ||||
|                         encryptFlag = apiEncrypt.response(); | ||||
|                     } else { | ||||
|                         // 是否有注解,有就报错,没有放行 | ||||
|                         HandlerExceptionResolver exceptionResolver = SpringUtils.getBean(HandlerExceptionResolver.class); | ||||
|                         exceptionResolver.resolveException( | ||||
|                             servletRequest, servletResponse, null, | ||||
|                             new ServiceException("没有访问权限,请联系管理员授权", HttpStatus.FORBIDDEN)); | ||||
|                     } | ||||
|                 } | ||||
|                 // 判断是否响应加密 | ||||
|                 if (encryptFlag) { | ||||
|                     responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse); | ||||
|                     responseWrapper = responseBodyWrapper; | ||||
|         // 是否为 put 或者 post 请求 | ||||
|         if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) { | ||||
|             // 是否存在加密标头 | ||||
|             String headerValue = servletRequest.getHeader(properties.getHeaderFlag()); | ||||
|             if (StringUtils.isNotBlank(headerValue)) { | ||||
|                 // 请求解密 | ||||
|                 requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag()); | ||||
|             } else { | ||||
|                 // 是否有注解,有就报错,没有放行 | ||||
|                 if (ObjectUtil.isNotNull(apiEncrypt)) { | ||||
|                     HandlerExceptionResolver exceptionResolver = SpringUtils.getBean("handlerExceptionResolver", HandlerExceptionResolver.class); | ||||
|                     exceptionResolver.resolveException( | ||||
|                         servletRequest, servletResponse, null, | ||||
|                         new ServiceException("没有访问权限,请联系管理员授权", HttpStatus.FORBIDDEN)); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 判断是否响应加密 | ||||
|         if (responseFlag) { | ||||
|             responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse); | ||||
|             responseWrapper = responseBodyWrapper; | ||||
|         } | ||||
|  | ||||
|         chain.doFilter( | ||||
|             ObjectUtil.defaultIfNull(requestWrapper, request), | ||||
|             ObjectUtil.defaultIfNull(responseWrapper, response)); | ||||
|  | ||||
|         if (encryptFlag) { | ||||
|         if (responseFlag) { | ||||
|             servletResponse.reset(); | ||||
|             // 对原始内容加密 | ||||
|             String encryptContent = responseBodyWrapper.getEncryptContent( | ||||
| @@ -104,7 +99,7 @@ public class CryptoFilter implements Filter { | ||||
|                 } | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             throw new RuntimeException(e); | ||||
|             return null; | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user