mirror of
				https://github.com/dromara/RuoYi-Vue-Plus.git
				synced 2025-11-04 00:03:51 +08:00 
			
		
		
		
	Compare commits
	
		
			634 Commits
		
	
	
		
			v5.1.0
			...
			4743eb1d3b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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 | ||
| 
						 | 
					203233fbaf | ||
| 
						 | 
					5bc6b1de3a | ||
| 
						 | 
					7445934371 | ||
| 
						 | 
					521596bc12 | ||
| 
						 | 
					00d85947b0 | ||
| 
						 | 
					a62eba5c37 | ||
| 
						 | 
					102d854743 | ||
| 
						 | 
					953de2fb1c | ||
| 
						 | 
					c241a715a4 | ||
| 
						 | 
					3a51c58b82 | ||
| 
						 | 
					0ac4a63e91 | ||
| 
						 | 
					e981e2f69f | ||
| 
						 | 
					296392b094 | ||
| 
						 | 
					e1e409d89e | ||
| 
						 | 
					7fb4d17913 | ||
| 
						 | 
					469c334b95 | ||
| 
						 | 
					ddcb13c7c3 | ||
| 
						 | 
					c4582b085f | ||
| 
						 | 
					45097ccb8f | ||
| 
						 | 
					663e22ac29 | ||
| 
						 | 
					0403c2c274 | ||
| 
						 | 
					48fdfffc42 | ||
| 
						 | 
					6037edb621 | ||
| 
						 | 
					ce8a82383d | ||
| 
						 | 
					e1390615b7 | ||
| 
						 | 
					860afb8738 | ||
| 
						 | 
					e509d95af9 | ||
| 
						 | 
					4cefcf6ada | ||
| 
						 | 
					affbce1957 | ||
| 
						 | 
					5d97541b8f | ||
| 
						 | 
					8ed5c75c6a | ||
| 
						 | 
					a1adfa2e93 | ||
| 
						 | 
					1999000322 | ||
| 
						 | 
					677fe7e2a5 | ||
| 
						 | 
					8704cae182 | ||
| 
						 | 
					7dde869eba | ||
| 
						 | 
					cb9a3c36e6 | ||
| 
						 | 
					d131a833ab | ||
| 
						 | 
					be1398137a | ||
| 
						 | 
					183e39f1cd | ||
| 
						 | 
					a6fc191df2 | ||
| 
						 | 
					87981fb80c | ||
| 
						 | 
					96ab48396c | ||
| 
						 | 
					40ec6584c5 | ||
| 
						 | 
					d811cb8e04 | ||
| 
						 | 
					d3ad832ded | ||
| 
						 | 
					b0cc1a913e | ||
| 
						 | 
					b0faebc5e6 | ||
| 
						 | 
					c45ffaec0f | ||
| 
						 | 
					78c91d0733 | ||
| 
						 | 
					3f1e5702a2 | ||
| 
						 | 
					6bed331971 | ||
| 
						 | 
					793737d69d | ||
| 
						 | 
					4e8f2b130e | ||
| 
						 | 
					ac6fe634dc | ||
| 
						 | 
					1e1616ceb0 | ||
| 
						 | 
					3046362ff4 | ||
| 
						 | 
					f2956c322e | ||
| 
						 | 
					754c22f8a6 | ||
| 
						 | 
					16685a8e0c | ||
| 
						 | 
					e2692aa9e9 | ||
| 
						 | 
					9b0938e0d6 | ||
| 
						 | 
					c4f69b466a | ||
| 
						 | 
					3acbf6efee | ||
| 
						 | 
					17cf957052 | ||
| 
						 | 
					624a14d2de | ||
| 
						 | 
					0682efb472 | ||
| 
						 | 
					abc3acf489 | ||
| 
						 | 
					f9ba1df798 | ||
| 
						 | 
					9c7274b776 | ||
| 
						 | 
					bf9f54c021 | ||
| 
						 | 
					26369657e3 | ||
| 
						 | 
					3d9b05a61a | ||
| 
						 | 
					c93b666140 | ||
| 
						 | 
					0f16051024 | ||
| 
						 | 
					81f7a59caa | ||
| 
						 | 
					2c4306b436 | ||
| 
						 | 
					3a9379555e | ||
| 
						 | 
					e7ed4afe79 | ||
| 
						 | 
					213cc8a3aa | ||
| 
						 | 
					323c290ee9 | ||
| 
						 | 
					5602976ee1 | ||
| 
						 | 
					c709b44786 | ||
| 
						 | 
					f34740ad03 | ||
| 
						 | 
					51feaf7b99 | ||
| 
						 | 
					c5bc6a97c9 | ||
| 
						 | 
					864e5d695f | ||
| 
						 | 
					635e36a882 | ||
| 
						 | 
					002a880e8b | ||
| 
						 | 
					5d58c27720 | ||
| 
						 | 
					2709690f81 | ||
| 
						 | 
					5f84ab968b | ||
| 
						 | 
					cf1c18184e | ||
| 
						 | 
					8d681eda21 | ||
| 
						 | 
					e2b169e07a | ||
| 
						 | 
					e58b010f4a | ||
| 
						 | 
					23e85c5b9c | ||
| 
						 | 
					4198dce4f0 | ||
| 
						 | 
					b537b1ecd2 | ||
| 
						 | 
					22a8057ea4 | ||
| 
						 | 
					f1232a60f3 | ||
| 
						 | 
					e2e7cee58b | ||
| 
						 | 
					7abd419d8c | ||
| 
						 | 
					3774d71bac | ||
| 
						 | 
					29118ae78c | ||
| 
						 | 
					f708492681 | ||
| 
						 | 
					bf34781f04 | ||
| 
						 | 
					0e6464d344 | ||
| 
						 | 
					de3adb2230 | ||
| 
						 | 
					653bf84929 | ||
| 
						 | 
					9e796943b8 | ||
| 
						 | 
					c4daabafbe | ||
| 
						 | 
					8b45cb1963 | ||
| 
						 | 
					ee5fc9507a | ||
| 
						 | 
					905637d70f | ||
| 
						 | 
					cb3ead140b | ||
| 
						 | 
					aabd3990a0 | ||
| 
						 | 
					937de85647 | ||
| 
						 | 
					be700f7ddc | ||
| 
						 | 
					b2eceed943 | ||
| 
						 | 
					40d652ab82 | ||
| 
						 | 
					b24e6348c3 | ||
| 
						 | 
					2c64c66ed1 | ||
| 
						 | 
					977e9a119c | ||
| 
						 | 
					bfe1b2ae50 | ||
| 
						 | 
					23b7e5f8c7 | ||
| 
						 | 
					de0fe48a72 | ||
| 
						 | 
					6bc2bac957 | ||
| 
						 | 
					0cd6712b2a | ||
| 
						 | 
					7e62401bac | ||
| 
						 | 
					3a67a6599f | ||
| 
						 | 
					6381034b1f | ||
| 
						 | 
					eb9e4b5eef | ||
| 
						 | 
					052a8f71a3 | ||
| 
						 | 
					58b4df7bb5 | 
@@ -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 {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
name: Bug 反馈
 | 
			
		||||
description: 当你中发现了一个 Bug,导致应用崩溃或抛出异常,或者有一个组件存在问题,或者某些地方看起来不对劲。
 | 
			
		||||
description: 当你使用过程中发现了一个 Bug,导致应用崩溃或抛出异常,或者有一个组件存在问题,或者某些地方看起来不对劲,请在这里反馈。
 | 
			
		||||
title: "[Bug]: "
 | 
			
		||||
labels: ["bug"]
 | 
			
		||||
body:
 | 
			
		||||
@@ -9,14 +9,15 @@ body:
 | 
			
		||||
      label: 版本
 | 
			
		||||
      description: 你当前正在使用我们软件的哪个版本(pom文件内的版本号)?
 | 
			
		||||
      value: |
 | 
			
		||||
        jdk版本(带上尾号): 例如 1.8.0
 | 
			
		||||
        框架版本(项目启动时输出的版本号): 例如 4.4.0
 | 
			
		||||
        注意: 未填写版本号不予处理直接关闭或删除
 | 
			
		||||
        jdk版本(带上尾号):
 | 
			
		||||
        框架版本(项目启动时输出的版本号):
 | 
			
		||||
        其他依赖版本(你觉得有必要的):
 | 
			
		||||
    validations:
 | 
			
		||||
      required: true
 | 
			
		||||
  - type: checkboxes
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: 功能不好用不会用是否已经看过项目文档?
 | 
			
		||||
      label: 功能不好用不会用是否已经看过项目文档?
 | 
			
		||||
      options:
 | 
			
		||||
        - label: https://plus-doc.dromara.org
 | 
			
		||||
          required: true
 | 
			
		||||
@@ -35,10 +36,10 @@ body:
 | 
			
		||||
  - type: markdown
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: 如何复现
 | 
			
		||||
      description: 请详细告诉我们如何复现你遇到的问题
 | 
			
		||||
      description: 请详细告诉我们如何复现你遇到的问题。
 | 
			
		||||
      value: |
 | 
			
		||||
        如涉及代码 可提供一个最小代码示例 并使用```附上它 或者截图均可 越详细越好<br>
 | 
			
		||||
        大多数问题都是 代码编写错误问题 逻辑问题 或者用法错误等问题
 | 
			
		||||
        如涉及代码,可提供一个最小代码示例,并使用```附上它,或者截图均可,越详细越好。<br>
 | 
			
		||||
        大多数问题都是:代码编写错误问题,逻辑问题,或者用法错误等问题。
 | 
			
		||||
    validations:
 | 
			
		||||
      required: true
 | 
			
		||||
  - type: textarea
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
name: 功能建议
 | 
			
		||||
description: 对本项目提出一个功能建议
 | 
			
		||||
description: 对本项目提出一个功能建议。
 | 
			
		||||
title: "[功能建议]: "
 | 
			
		||||
labels: ["enhancement"]
 | 
			
		||||
body:
 | 
			
		||||
@@ -39,5 +39,5 @@ body:
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: 意向参与贡献
 | 
			
		||||
      options:
 | 
			
		||||
        - label: 我有意向参与具体功能的开发实现并将代码贡献回到上游社区
 | 
			
		||||
          required: false
 | 
			
		||||
        - label: 我有意向参与具体功能的开发实现并将代码贡献回到上游社区。
 | 
			
		||||
          required: false
 | 
			
		||||
 
 | 
			
		||||
@@ -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.0" />
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.2.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.0" />
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-server:5.2.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.0" />
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.2.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" />
 | 
			
		||||
							
								
								
									
										45
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								README.md
									
									
									
									
									
								
							@@ -9,10 +9,10 @@
 | 
			
		||||
[](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 针对 `分布式集群与多租户` 场景全方位升级(不兼容原框架)
 | 
			
		||||
 | 
			
		||||
@@ -23,7 +23,15 @@
 | 
			
		||||
 | 
			
		||||
> 前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)
 | 
			
		||||
 | 
			
		||||
> 文档地址: [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>
 | 
			
		||||
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
 | 
			
		||||
 | 
			
		||||
# 本框架与RuoYi的功能差异
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +44,7 @@
 | 
			
		||||
| 权限认证        | 采用 Sa-Token、Jwt 静态使用功能齐全 低耦合 高扩展                                                                                  | Spring Security 配置繁琐扩展性极差                                                          |
 | 
			
		||||
| 权限注解        | 采用 Sa-Token 支持注解 登录校验、角色校验、权限校验、二级认证校验、HttpBasic校验、忽略校验<br/>角色与权限校验支持多种条件 如 `AND` `OR` 或 `权限 OR 角色` 等复杂表达式        | 只支持是否存在匹配                                                                          |
 | 
			
		||||
| 三方鉴权        | 采用 JustAuth 第三方登录组件 支持微信、钉钉等数十种三方认证                                                                               | 无                                                                                  |
 | 
			
		||||
| 关系数据库支持     | 原生支持 MySQL、Oracle、PostgreSQL、SQLServer<br/>可同时使用异构切换                                                              | 支持 Mysql、Oracle 不支持同时使用、不支持异构切换                                                    |
 | 
			
		||||
| 关系数据库支持     | 原生支持 MySQL、Oracle、PostgreSQL、SQLServer<br/>可同时使用异构切换(支持其他 mybatis-plus 支持的所有数据库 只需要增加jdbc依赖即可使用 达梦金仓等均有成功案例)      | 支持 Mysql、Oracle 不支持同时使用、不支持异构切换                                                    |
 | 
			
		||||
| 缓存数据库       | 支持 Redis 5-7 支持大部分新功能特性 如 分布式限流、分布式队列                                                                             | Redis 简单 get set 支持                                                                |
 | 
			
		||||
| Redis客户端    | 采用 Redisson Redis官方推荐 基于Netty的客户端工具<br/>支持Redis 90%以上的命令 底层优化规避很多不正确的用法 例如: keys被转换为scan<br/>支持单机、哨兵、单主集群、多主集群等模式 | Lettuce + RedisTemplate 支持模式少 工具使用繁琐<br/>连接池采用 common-pool Bug多经常性出问题              |
 | 
			
		||||
| 缓存注解        | 采用 Spring-Cache 注解 对其扩展了实现支持了更多功能<br/>例如 过期时间 最大空闲时间 组最大长度等 只需一个注解即可完成数据自动缓存                                      | 需手动编写Redis代码逻辑                                                                     |
 | 
			
		||||
@@ -48,15 +56,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 +73,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 +125,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 +140,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" />
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										169
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								pom.xml
									
									
									
									
									
								
							@@ -13,43 +13,46 @@
 | 
			
		||||
    <description>RuoYi-Vue-Plus多租户管理系统</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <revision>5.1.0</revision>
 | 
			
		||||
        <spring-boot.version>3.1.3</spring-boot.version>
 | 
			
		||||
        <revision>5.2.1</revision>
 | 
			
		||||
        <spring-boot.version>3.2.8</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.2</spring-boot.mybatis>
 | 
			
		||||
        <springdoc.version>2.2.0</springdoc.version>
 | 
			
		||||
        <mybatis.version>3.5.16</mybatis.version>
 | 
			
		||||
        <springdoc.version>2.6.0</springdoc.version>
 | 
			
		||||
        <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
 | 
			
		||||
        <poi.version>5.2.3</poi.version>
 | 
			
		||||
        <easyexcel.version>3.3.2</easyexcel.version>
 | 
			
		||||
        <easyexcel.version>4.0.2</easyexcel.version>
 | 
			
		||||
        <velocity.version>2.3</velocity.version>
 | 
			
		||||
        <satoken.version>1.35.0.RC</satoken.version>
 | 
			
		||||
        <mybatis-plus.version>3.5.3.2</mybatis-plus.version>
 | 
			
		||||
        <satoken.version>1.38.0</satoken.version>
 | 
			
		||||
        <mybatis-plus.version>3.5.7</mybatis-plus.version>
 | 
			
		||||
        <p6spy.version>3.9.1</p6spy.version>
 | 
			
		||||
        <hutool.version>5.8.20</hutool.version>
 | 
			
		||||
        <hutool.version>5.8.31</hutool.version>
 | 
			
		||||
        <okhttp.version>4.10.0</okhttp.version>
 | 
			
		||||
        <spring-boot-admin.version>3.1.5</spring-boot-admin.version>
 | 
			
		||||
        <redisson.version>3.23.4</redisson.version>
 | 
			
		||||
        <lock4j.version>2.2.5</lock4j.version>
 | 
			
		||||
        <dynamic-ds.version>4.1.3</dynamic-ds.version>
 | 
			
		||||
        <alibaba-ttl.version>2.14.2</alibaba-ttl.version>
 | 
			
		||||
        <powerjob.version>4.3.3</powerjob.version>
 | 
			
		||||
        <mapstruct-plus.version>1.3.5</mapstruct-plus.version>
 | 
			
		||||
        <spring-boot-admin.version>3.2.3</spring-boot-admin.version>
 | 
			
		||||
        <redisson.version>3.34.1</redisson.version>
 | 
			
		||||
        <lock4j.version>2.2.7</lock4j.version>
 | 
			
		||||
        <dynamic-ds.version>4.3.1</dynamic-ds.version>
 | 
			
		||||
        <snailjob.version>1.1.1</snailjob.version>
 | 
			
		||||
        <mapstruct-plus.version>1.4.3</mapstruct-plus.version>
 | 
			
		||||
        <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
 | 
			
		||||
        <lombok.version>1.18.28</lombok.version>
 | 
			
		||||
        <bouncycastle.version>1.72</bouncycastle.version>
 | 
			
		||||
        <justauth.version>1.16.5</justauth.version>
 | 
			
		||||
        <lombok.version>1.18.34</lombok.version>
 | 
			
		||||
        <bouncycastle.version>1.76</bouncycastle.version>
 | 
			
		||||
        <justauth.version>1.16.6</justauth.version>
 | 
			
		||||
        <!-- 离线IP地址定位库 -->
 | 
			
		||||
        <ip2region.version>2.7.0</ip2region.version>
 | 
			
		||||
 | 
			
		||||
        <!-- 临时修复 snakeyaml 漏洞 -->
 | 
			
		||||
        <snakeyaml.version>1.33</snakeyaml.version>
 | 
			
		||||
        <undertow.version>2.3.15.Final</undertow.version>
 | 
			
		||||
 | 
			
		||||
        <!-- OSS 配置 -->
 | 
			
		||||
        <aws-java-sdk-s3.version>1.12.540</aws-java-sdk-s3.version>
 | 
			
		||||
        <aws.sdk.version>2.25.15</aws.sdk.version>
 | 
			
		||||
        <aws.crt.version>0.29.13</aws.crt.version>
 | 
			
		||||
        <!-- SMS 配置 -->
 | 
			
		||||
        <sms4j.version>2.2.0</sms4j.version>
 | 
			
		||||
        <sms4j.version>3.3.2</sms4j.version>
 | 
			
		||||
        <!-- 限制框架中的fastjson版本 -->
 | 
			
		||||
        <fastjson.version>1.2.83</fastjson.version>
 | 
			
		||||
        <!-- 面向运行时的D-ORM依赖 -->
 | 
			
		||||
        <anyline.version>8.7.2-20240808</anyline.version>
 | 
			
		||||
        <!--工作流配置-->
 | 
			
		||||
        <flowable.version>7.0.1</flowable.version>
 | 
			
		||||
 | 
			
		||||
        <!-- 插件版本 -->
 | 
			
		||||
        <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
 | 
			
		||||
@@ -65,7 +68,7 @@
 | 
			
		||||
            <properties>
 | 
			
		||||
                <!-- 环境标识,需要与配置文件的名称相对应 -->
 | 
			
		||||
                <profiles.active>local</profiles.active>
 | 
			
		||||
                <logging.level>debug</logging.level>
 | 
			
		||||
                <logging.level>info</logging.level>
 | 
			
		||||
            </properties>
 | 
			
		||||
        </profile>
 | 
			
		||||
        <profile>
 | 
			
		||||
@@ -73,7 +76,7 @@
 | 
			
		||||
            <properties>
 | 
			
		||||
                <!-- 环境标识,需要与配置文件的名称相对应 -->
 | 
			
		||||
                <profiles.active>dev</profiles.active>
 | 
			
		||||
                <logging.level>debug</logging.level>
 | 
			
		||||
                <logging.level>info</logging.level>
 | 
			
		||||
            </properties>
 | 
			
		||||
            <activation>
 | 
			
		||||
                <!-- 默认环境 -->
 | 
			
		||||
@@ -111,6 +114,14 @@
 | 
			
		||||
                <scope>import</scope>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.flowable</groupId>
 | 
			
		||||
                <artifactId>flowable-bom</artifactId>
 | 
			
		||||
                <version>${flowable.version}</version>
 | 
			
		||||
                <type>pom</type>
 | 
			
		||||
                <scope>import</scope>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <!-- JustAuth 的依赖配置-->
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>me.zhyd.oauth</groupId>
 | 
			
		||||
@@ -145,26 +156,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代码生成使用模板 -->
 | 
			
		||||
@@ -206,14 +201,14 @@
 | 
			
		||||
            </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>
 | 
			
		||||
 | 
			
		||||
@@ -236,10 +231,23 @@
 | 
			
		||||
                <version>${okhttp.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <!--  AWS SDK for Java 2.x  -->
 | 
			
		||||
            <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</artifactId>
 | 
			
		||||
                <version>${aws.sdk.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <!-- 使用AWS基于 CRT 的 S3 客户端 -->
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>software.amazon.awssdk.crt</groupId>
 | 
			
		||||
                <artifactId>aws-crt</artifactId>
 | 
			
		||||
                <version>${aws.crt.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>software.amazon.awssdk</groupId>
 | 
			
		||||
                <artifactId>s3-transfer-manager</artifactId>
 | 
			
		||||
                <version>${aws.sdk.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <!--短信sms4j-->
 | 
			
		||||
            <dependency>
 | 
			
		||||
@@ -272,29 +280,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>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <!-- 临时修复 snakeyaml 漏洞 -->
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.yaml</groupId>
 | 
			
		||||
                <artifactId>snakeyaml</artifactId>
 | 
			
		||||
                <version>${snakeyaml.version}</version>
 | 
			
		||||
                <groupId>com.aizuda</groupId>
 | 
			
		||||
                <artifactId>snail-job-client-job-core</artifactId>
 | 
			
		||||
                <version>${snailjob.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <!-- 加密包引入 -->
 | 
			
		||||
@@ -317,6 +312,34 @@
 | 
			
		||||
                <version>${ip2region.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>io.undertow</groupId>
 | 
			
		||||
                <artifactId>undertow-core</artifactId>
 | 
			
		||||
                <version>${undertow.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>io.undertow</groupId>
 | 
			
		||||
                <artifactId>undertow-servlet</artifactId>
 | 
			
		||||
                <version>${undertow.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>io.undertow</groupId>
 | 
			
		||||
                <artifactId>undertow-websockets-jsr</artifactId>
 | 
			
		||||
                <version>${undertow.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <artifactId>commons-compress</artifactId>
 | 
			
		||||
                <groupId>org.apache.commons</groupId>
 | 
			
		||||
                <version>1.26.2</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>com.alibaba</groupId>
 | 
			
		||||
                <artifactId>fastjson</artifactId>
 | 
			
		||||
                <version>${fastjson.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.dromara</groupId>
 | 
			
		||||
                <artifactId>ruoyi-system</artifactId>
 | 
			
		||||
@@ -341,6 +364,13 @@
 | 
			
		||||
                <version>${revision}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <!--  工作流模块  -->
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.dromara</groupId>
 | 
			
		||||
                <artifactId>ruoyi-workflow</artifactId>
 | 
			
		||||
                <version>${revision}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
        </dependencies>
 | 
			
		||||
    </dependencyManagement>
 | 
			
		||||
 | 
			
		||||
@@ -400,6 +430,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,6 +1,9 @@
 | 
			
		||||
FROM findepi/graalvm:java17-native
 | 
			
		||||
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
 | 
			
		||||
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
 | 
			
		||||
#FROM bellsoft/liberica-openjdk-debian:21.0.3-cds
 | 
			
		||||
#FROM findepi/graalvm:java17-native
 | 
			
		||||
 | 
			
		||||
MAINTAINER Lion Li
 | 
			
		||||
LABEL maintainer="Lion Li"
 | 
			
		||||
 | 
			
		||||
RUN mkdir -p /ruoyi/server/logs \
 | 
			
		||||
    /ruoyi/server/temp \
 | 
			
		||||
@@ -8,16 +11,16 @@ RUN mkdir -p /ruoyi/server/logs \
 | 
			
		||||
 | 
			
		||||
WORKDIR /ruoyi/server
 | 
			
		||||
 | 
			
		||||
ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
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"]
 | 
			
		||||
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 \
 | 
			
		||||
           -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>
 | 
			
		||||
@@ -48,6 +55,15 @@
 | 
			
		||||
            <artifactId>ruoyi-common-social</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.dromara</groupId>
 | 
			
		||||
            <artifactId>ruoyi-common-ratelimiter</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.dromara</groupId>
 | 
			
		||||
            <artifactId>ruoyi-common-mail</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.dromara</groupId>
 | 
			
		||||
@@ -71,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>
 | 
			
		||||
@@ -82,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,8 @@
 | 
			
		||||
package org.dromara.web.controller;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.annotation.SaIgnore;
 | 
			
		||||
import cn.dev33.satoken.exception.NotLoginException;
 | 
			
		||||
import cn.hutool.core.codec.Base64;
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
@@ -10,19 +12,23 @@ 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.domain.R;
 | 
			
		||||
import org.dromara.common.core.domain.model.LoginBody;
 | 
			
		||||
import org.dromara.common.core.domain.model.RegisterBody;
 | 
			
		||||
import org.dromara.common.core.utils.MapstructUtils;
 | 
			
		||||
import org.dromara.common.core.utils.MessageUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StreamUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.core.domain.model.SocialLoginBody;
 | 
			
		||||
import org.dromara.common.core.utils.*;
 | 
			
		||||
import org.dromara.common.encrypt.annotation.ApiEncrypt;
 | 
			
		||||
import org.dromara.common.json.utils.JsonUtils;
 | 
			
		||||
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.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;
 | 
			
		||||
@@ -38,7 +44,12 @@ 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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 认证
 | 
			
		||||
@@ -47,7 +58,6 @@ import java.util.List;
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
@SaIgnore
 | 
			
		||||
@Validated
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/auth")
 | 
			
		||||
@@ -60,29 +70,44 @@ public class AuthController {
 | 
			
		||||
    private final ISysTenantService tenantService;
 | 
			
		||||
    private final ISysSocialService socialUserService;
 | 
			
		||||
    private final ISysClientService clientService;
 | 
			
		||||
    private final ScheduledExecutorService scheduledExecutorService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 登录方法
 | 
			
		||||
     *
 | 
			
		||||
     * @param loginBody 登录信息
 | 
			
		||||
     * @param body 登录信息
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    @ApiEncrypt
 | 
			
		||||
    @PostMapping("/login")
 | 
			
		||||
    public R<LoginVo> login(@Validated @RequestBody LoginBody loginBody) {
 | 
			
		||||
    public R<LoginVo> login(@RequestBody String body) {
 | 
			
		||||
        LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);
 | 
			
		||||
        ValidatorUtils.validate(loginBody);
 | 
			
		||||
        // 授权类型和客户端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())) {
 | 
			
		||||
            return R.fail(MessageUtils.message("auth.grant.type.blocked"));
 | 
			
		||||
        }
 | 
			
		||||
        // 校验租户
 | 
			
		||||
        loginService.checkTenant(loginBody.getTenantId());
 | 
			
		||||
        // 登录
 | 
			
		||||
        return R.ok(IAuthStrategy.login(loginBody, client));
 | 
			
		||||
        LoginVo loginVo = IAuthStrategy.login(body, client, grantType);
 | 
			
		||||
 | 
			
		||||
        Long userId = LoginHelper.getUserId();
 | 
			
		||||
        scheduledExecutorService.schedule(() -> {
 | 
			
		||||
            SseMessageDto dto = new SseMessageDto();
 | 
			
		||||
            dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统");
 | 
			
		||||
            dto.setUserIds(List.of(userId));
 | 
			
		||||
            SseMessageUtils.publishMessage(dto);
 | 
			
		||||
        }, 5, TimeUnit.SECONDS);
 | 
			
		||||
        return R.ok(loginVo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -92,13 +117,18 @@ public class AuthController {
 | 
			
		||||
     * @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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -109,9 +139,11 @@ public class AuthController {
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    @PostMapping("/social/callback")
 | 
			
		||||
    public R<Void> socialCallback(@RequestBody LoginBody loginBody) {
 | 
			
		||||
    public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
 | 
			
		||||
        // 获取第三方登录信息
 | 
			
		||||
        AuthResponse<AuthUser> response = SocialUtils.loginAuth(loginBody, socialProperties);
 | 
			
		||||
        AuthResponse<AuthUser> response = SocialUtils.loginAuth(
 | 
			
		||||
                loginBody.getSource(), loginBody.getSocialCode(),
 | 
			
		||||
                loginBody.getSocialState(), socialProperties);
 | 
			
		||||
        AuthUser authUserData = response.getData();
 | 
			
		||||
        // 判断授权响应是否成功
 | 
			
		||||
        if (!response.ok()) {
 | 
			
		||||
@@ -146,6 +178,7 @@ public class AuthController {
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户注册
 | 
			
		||||
     */
 | 
			
		||||
    @ApiEncrypt
 | 
			
		||||
    @PostMapping("/register")
 | 
			
		||||
    public R<Void> register(@Validated @RequestBody RegisterBody user) {
 | 
			
		||||
        if (!configService.selectRegisterEnabled(user.getTenantId())) {
 | 
			
		||||
@@ -162,8 +195,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");
 | 
			
		||||
@@ -175,12 +226,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.equals(vo.getDomain(), host));
 | 
			
		||||
        result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
 | 
			
		||||
        return R.ok(result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,9 @@ 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;
 | 
			
		||||
@@ -13,17 +16,15 @@ import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
 | 
			
		||||
import org.dromara.common.mail.config.properties.MailProperties;
 | 
			
		||||
import org.dromara.common.mail.utils.MailUtils;
 | 
			
		||||
import org.dromara.common.ratelimiter.annotation.RateLimiter;
 | 
			
		||||
import org.dromara.common.ratelimiter.enums.LimitType;
 | 
			
		||||
import org.dromara.common.redis.utils.RedisUtils;
 | 
			
		||||
import org.dromara.common.web.config.properties.CaptchaProperties;
 | 
			
		||||
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;
 | 
			
		||||
@@ -54,6 +55,7 @@ public class CaptchaController {
 | 
			
		||||
     *
 | 
			
		||||
     * @param phonenumber 用户手机号
 | 
			
		||||
     */
 | 
			
		||||
    @RateLimiter(key = "#phonenumber", time = 60, count = 1)
 | 
			
		||||
    @GetMapping("/resource/sms/code")
 | 
			
		||||
    public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
 | 
			
		||||
        String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
 | 
			
		||||
@@ -63,11 +65,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();
 | 
			
		||||
    }
 | 
			
		||||
@@ -77,6 +79,7 @@ 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()) {
 | 
			
		||||
@@ -97,6 +100,7 @@ public class CaptchaController {
 | 
			
		||||
    /**
 | 
			
		||||
     * 生成验证码
 | 
			
		||||
     */
 | 
			
		||||
    @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
 | 
			
		||||
    @GetMapping("/auth/code")
 | 
			
		||||
    public R<CaptchaVo> getCode() {
 | 
			
		||||
        CaptchaVo captchaVo = new CaptchaVo();
 | 
			
		||||
@@ -116,6 +120,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();
 | 
			
		||||
 
 | 
			
		||||
@@ -13,10 +13,19 @@ import lombok.Data;
 | 
			
		||||
@AutoMapper(target = SysTenantVo.class)
 | 
			
		||||
public class TenantListVo {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 租户编号
 | 
			
		||||
     */
 | 
			
		||||
    private String tenantId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 企业名称
 | 
			
		||||
     */
 | 
			
		||||
    private String companyName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 域名
 | 
			
		||||
     */
 | 
			
		||||
    private String domain;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,26 @@
 | 
			
		||||
package org.dromara.common.satoken.listener;
 | 
			
		||||
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 org.dromara.common.core.constant.CacheConstants;
 | 
			
		||||
import org.dromara.common.core.domain.dto.UserOnlineDTO;
 | 
			
		||||
import org.dromara.common.core.domain.model.LoginUser;
 | 
			
		||||
import org.dromara.common.core.enums.UserType;
 | 
			
		||||
import org.dromara.common.redis.utils.RedisUtils;
 | 
			
		||||
import org.dromara.common.satoken.utils.LoginHelper;
 | 
			
		||||
import org.dromara.common.core.utils.ip.AddressUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ServletUtils;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
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.utils.MessageUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ServletUtils;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
@@ -30,35 +36,46 @@ import java.time.Duration;
 | 
			
		||||
public class UserActionListener implements SaTokenListener {
 | 
			
		||||
 | 
			
		||||
    private final SaTokenConfig tokenConfig;
 | 
			
		||||
    private final SysLoginService loginService;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 每次登录时触发
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
 | 
			
		||||
        UserType userType = UserType.getUserType(loginId.toString());
 | 
			
		||||
        if (userType == UserType.SYS_USER) {
 | 
			
		||||
            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));
 | 
			
		||||
            dto.setBrowser(userAgent.getBrowser().getName());
 | 
			
		||||
            dto.setOs(userAgent.getOs().getName());
 | 
			
		||||
            dto.setLoginTime(System.currentTimeMillis());
 | 
			
		||||
            dto.setTokenId(tokenValue);
 | 
			
		||||
            dto.setUserName(user.getUsername());
 | 
			
		||||
            dto.setDeptName(user.getDeptName());
 | 
			
		||||
        UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
 | 
			
		||||
        String ip = ServletUtils.getClientIP();
 | 
			
		||||
        UserOnlineDTO dto = new UserOnlineDTO();
 | 
			
		||||
        dto.setIpaddr(ip);
 | 
			
		||||
        dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
 | 
			
		||||
        dto.setBrowser(userAgent.getBrowser().getName());
 | 
			
		||||
        dto.setOs(userAgent.getOs().getName());
 | 
			
		||||
        dto.setLoginTime(System.currentTimeMillis());
 | 
			
		||||
        dto.setTokenId(tokenValue);
 | 
			
		||||
        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()));
 | 
			
		||||
            }
 | 
			
		||||
            log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
 | 
			
		||||
        } else if (userType == UserType.APP_USER) {
 | 
			
		||||
            // app端 自行根据业务编写
 | 
			
		||||
        }
 | 
			
		||||
        });
 | 
			
		||||
        // 记录登录日志
 | 
			
		||||
        LogininforEvent logininforEvent = new LogininforEvent();
 | 
			
		||||
        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((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
 | 
			
		||||
        log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -66,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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -75,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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -84,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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
package org.dromara.web.service;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import org.dromara.common.core.domain.model.LoginBody;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -18,28 +18,29 @@ public interface IAuthStrategy {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 登录
 | 
			
		||||
     *
 | 
			
		||||
     * @param body      登录对象
 | 
			
		||||
     * @param client    授权管理视图对象
 | 
			
		||||
     * @param grantType 授权类型
 | 
			
		||||
     * @return 登录验证信息
 | 
			
		||||
     */
 | 
			
		||||
    static LoginVo login(LoginBody loginBody, SysClient client) {
 | 
			
		||||
    static LoginVo login(String body, SysClientVo client, String grantType) {
 | 
			
		||||
        // 授权类型和客户端id
 | 
			
		||||
        String clientId = loginBody.getClientId();
 | 
			
		||||
        String grantType = loginBody.getGrantType();
 | 
			
		||||
        String beanName = grantType + BASE_NAME;
 | 
			
		||||
        if (!SpringUtils.containsBean(beanName)) {
 | 
			
		||||
            throw new ServiceException("授权类型不正确!");
 | 
			
		||||
        }
 | 
			
		||||
        IAuthStrategy instance = SpringUtils.getBean(beanName);
 | 
			
		||||
        instance.validate(loginBody);
 | 
			
		||||
        return instance.login(clientId, loginBody, client);
 | 
			
		||||
        return instance.login(body, client);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 参数校验
 | 
			
		||||
     */
 | 
			
		||||
    void validate(LoginBody loginBody);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 登录
 | 
			
		||||
     *
 | 
			
		||||
     * @param body   登录对象
 | 
			
		||||
     * @param client 授权管理视图对象
 | 
			
		||||
     * @return 登录验证信息
 | 
			
		||||
     */
 | 
			
		||||
    LoginVo login(String clientId, LoginBody loginBody, SysClient client);
 | 
			
		||||
    LoginVo login(String body, SysClientVo client);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,36 +3,34 @@ package org.dromara.web.service;
 | 
			
		||||
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.TenantConstants;
 | 
			
		||||
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.DateUtils;
 | 
			
		||||
import org.dromara.common.core.utils.MessageUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ServletUtils;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.*;
 | 
			
		||||
import org.dromara.common.log.event.LogininforEvent;
 | 
			
		||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
 | 
			
		||||
import org.dromara.common.redis.utils.RedisUtils;
 | 
			
		||||
import org.dromara.common.satoken.utils.LoginHelper;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
@@ -60,6 +58,8 @@ 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 SysUserMapper userMapper;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -67,27 +67,37 @@ 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("此三方账号已经被绑定!");
 | 
			
		||||
        }
 | 
			
		||||
        // 查询是否已经绑定用户
 | 
			
		||||
        SysSocialVo vo = sysSocialService.selectByAuthId(authId);
 | 
			
		||||
        if (ObjectUtil.isEmpty(vo)) {
 | 
			
		||||
        SysSocialBo params = new SysSocialBo();
 | 
			
		||||
        params.setUserId(userId);
 | 
			
		||||
        params.setSource(bo.getSource());
 | 
			
		||||
        List<SysSocialVo> list = sysSocialService.queryList(params);
 | 
			
		||||
        if (CollUtil.isEmpty(list)) {
 | 
			
		||||
            // 没有绑定用户, 新增用户信息
 | 
			
		||||
            sysSocialService.insertByBo(bo);
 | 
			
		||||
        } else {
 | 
			
		||||
            // 更新用户信息
 | 
			
		||||
            bo.setId(vo.getId());
 | 
			
		||||
            bo.setId(list.get(0).getId());
 | 
			
		||||
            sysSocialService.updateByBo(bo);
 | 
			
		||||
            // 如果要绑定的平台账号已经被绑定过了 是否抛异常自行决断
 | 
			
		||||
            // throw new ServiceException("此平台账号已经被绑定!");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -98,6 +108,9 @@ public class SysLoginService {
 | 
			
		||||
    public void logout() {
 | 
			
		||||
        try {
 | 
			
		||||
            LoginUser loginUser = LoginHelper.getLoginUser();
 | 
			
		||||
            if (ObjectUtil.isNull(loginUser)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
 | 
			
		||||
                // 超级管理员 登出清除动态租户
 | 
			
		||||
                TenantHelper.clearDynamic();
 | 
			
		||||
@@ -130,7 +143,6 @@ public class SysLoginService {
 | 
			
		||||
        SpringUtils.context().publishEvent(logininforEvent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 构建登录用户
 | 
			
		||||
     */
 | 
			
		||||
@@ -144,9 +156,13 @@ public class SysLoginService {
 | 
			
		||||
        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);
 | 
			
		||||
        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(user.getUserId());
 | 
			
		||||
        loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
 | 
			
		||||
        return loginUser;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -155,20 +171,20 @@ public class SysLoginService {
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId 用户ID
 | 
			
		||||
     */
 | 
			
		||||
    public void recordLoginInfo(Long userId) {
 | 
			
		||||
    public void recordLoginInfo(Long userId, String ip) {
 | 
			
		||||
        SysUser sysUser = new SysUser();
 | 
			
		||||
        sysUser.setUserId(userId);
 | 
			
		||||
        sysUser.setLoginIp(ServletUtils.getClientIP());
 | 
			
		||||
        sysUser.setLoginIp(ip);
 | 
			
		||||
        sysUser.setLoginDate(DateUtils.getNowDate());
 | 
			
		||||
        sysUser.setUpdateBy(userId);
 | 
			
		||||
        userMapper.updateById(sysUser);
 | 
			
		||||
        DataPermissionHelper.ignore(() -> userMapper.updateById(sysUser));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 登录校验
 | 
			
		||||
     */
 | 
			
		||||
    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)
 | 
			
		||||
@@ -210,6 +226,9 @@ public class SysLoginService {
 | 
			
		||||
        if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (StringUtils.isBlank(tenantId)) {
 | 
			
		||||
            throw new TenantException("tenant.number.not.blank");
 | 
			
		||||
        }
 | 
			
		||||
        SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
 | 
			
		||||
        if (ObjectUtil.isNull(tenant)) {
 | 
			
		||||
            log.info("登录租户:{} 不存在.", tenantId);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
package org.dromara.web.service;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.secure.BCrypt;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.constant.Constants;
 | 
			
		||||
import org.dromara.common.core.constant.GlobalConstants;
 | 
			
		||||
import org.dromara.common.core.domain.model.RegisterBody;
 | 
			
		||||
@@ -14,10 +16,12 @@ import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.log.event.LogininforEvent;
 | 
			
		||||
import org.dromara.common.redis.utils.RedisUtils;
 | 
			
		||||
import org.dromara.common.tenant.helper.TenantHelper;
 | 
			
		||||
import org.dromara.common.web.config.properties.CaptchaProperties;
 | 
			
		||||
import org.dromara.system.domain.SysUser;
 | 
			
		||||
import org.dromara.system.domain.bo.SysUserBo;
 | 
			
		||||
import org.dromara.system.mapper.SysUserMapper;
 | 
			
		||||
import org.dromara.system.service.ISysUserService;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -30,6 +34,7 @@ import org.springframework.stereotype.Service;
 | 
			
		||||
public class SysRegisterService {
 | 
			
		||||
 | 
			
		||||
    private final ISysUserService userService;
 | 
			
		||||
    private final SysUserMapper userMapper;
 | 
			
		||||
    private final CaptchaProperties captchaProperties;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -53,7 +58,11 @@ public class SysRegisterService {
 | 
			
		||||
        sysUser.setPassword(BCrypt.hashpw(password));
 | 
			
		||||
        sysUser.setUserType(userType);
 | 
			
		||||
 | 
			
		||||
        if (!userService.checkUserNameUnique(sysUser)) {
 | 
			
		||||
        boolean exist = TenantHelper.dynamic(tenantId, () -> {
 | 
			
		||||
            return userMapper.exists(new LambdaQueryWrapper<SysUser>()
 | 
			
		||||
                .eq(SysUser::getUserName, sysUser.getUserName()));
 | 
			
		||||
        });
 | 
			
		||||
        if (exist) {
 | 
			
		||||
            throw new UserException("user.register.save.error", username);
 | 
			
		||||
        }
 | 
			
		||||
        boolean regFlag = userService.registerUser(sysUser, tenantId);
 | 
			
		||||
@@ -71,7 +80,7 @@ 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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ 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.model.LoginBody;
 | 
			
		||||
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;
 | 
			
		||||
@@ -17,12 +17,12 @@ import org.dromara.common.core.exception.user.UserException;
 | 
			
		||||
import org.dromara.common.core.utils.MessageUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ValidatorUtils;
 | 
			
		||||
import org.dromara.common.core.validate.auth.EmailGroup;
 | 
			
		||||
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,39 +44,34 @@ public class EmailAuthStrategy implements IAuthStrategy {
 | 
			
		||||
    private final SysUserMapper userMapper;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void validate(LoginBody loginBody) {
 | 
			
		||||
        ValidatorUtils.validate(loginBody, EmailGroup.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public LoginVo login(String clientId, LoginBody loginBody, 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();
 | 
			
		||||
        model.setDevice(client.getDeviceType());
 | 
			
		||||
        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
 | 
			
		||||
        // 例如: 后台用户30分钟过期 app用户1天过期
 | 
			
		||||
        model.setTimeout(client.getTimeout());
 | 
			
		||||
        model.setActiveTimeout(client.getActiveTimeout());
 | 
			
		||||
        model.setExtra(LoginHelper.CLIENT_KEY, clientId);
 | 
			
		||||
        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
 | 
			
		||||
        // 生成token
 | 
			
		||||
        LoginHelper.login(loginUser, model);
 | 
			
		||||
 | 
			
		||||
        loginService.recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
 | 
			
		||||
        loginService.recordLoginInfo(user.getUserId());
 | 
			
		||||
 | 
			
		||||
        LoginVo loginVo = new LoginVo();
 | 
			
		||||
        loginVo.setAccessToken(StpUtil.getTokenValue());
 | 
			
		||||
        loginVo.setExpireIn(StpUtil.getTokenTimeout());
 | 
			
		||||
        loginVo.setClientId(clientId);
 | 
			
		||||
        loginVo.setClientId(client.getClientId());
 | 
			
		||||
        return loginVo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -92,11 +87,8 @@ public class EmailAuthStrategy implements IAuthStrategy {
 | 
			
		||||
        return code.equals(emailCode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SysUserVo loadUserByEmail(String tenantId, String email) {
 | 
			
		||||
        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
 | 
			
		||||
            .select(SysUser::getEmail, SysUser::getStatus)
 | 
			
		||||
            .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
 | 
			
		||||
            .eq(SysUser::getEmail, 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);
 | 
			
		||||
@@ -104,10 +96,7 @@ public class EmailAuthStrategy implements IAuthStrategy {
 | 
			
		||||
            log.info("登录用户:{} 已被停用.", email);
 | 
			
		||||
            throw new UserException("user.blocked", email);
 | 
			
		||||
        }
 | 
			
		||||
        if (TenantHelper.isEnable()) {
 | 
			
		||||
            return userMapper.selectTenantUserByEmail(email, tenantId);
 | 
			
		||||
        }
 | 
			
		||||
        return userMapper.selectUserByEmail(email);
 | 
			
		||||
        return user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,8 @@ 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.model.LoginBody;
 | 
			
		||||
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;
 | 
			
		||||
@@ -19,13 +19,13 @@ import org.dromara.common.core.exception.user.UserException;
 | 
			
		||||
import org.dromara.common.core.utils.MessageUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ValidatorUtils;
 | 
			
		||||
import org.dromara.common.core.validate.auth.PasswordGroup;
 | 
			
		||||
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.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,12 +48,9 @@ public class PasswordAuthStrategy implements IAuthStrategy {
 | 
			
		||||
    private final SysUserMapper userMapper;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void validate(LoginBody loginBody) {
 | 
			
		||||
        ValidatorUtils.validate(loginBody, PasswordGroup.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public LoginVo login(String clientId, LoginBody loginBody, SysClient client) {
 | 
			
		||||
    public LoginVo login(String body, SysClientVo client) {
 | 
			
		||||
        PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
 | 
			
		||||
        ValidatorUtils.validate(loginBody);
 | 
			
		||||
        String tenantId = loginBody.getTenantId();
 | 
			
		||||
        String username = loginBody.getUsername();
 | 
			
		||||
        String password = loginBody.getPassword();
 | 
			
		||||
@@ -65,28 +62,28 @@ 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();
 | 
			
		||||
        model.setDevice(client.getDeviceType());
 | 
			
		||||
        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
 | 
			
		||||
        // 例如: 后台用户30分钟过期 app用户1天过期
 | 
			
		||||
        model.setTimeout(client.getTimeout());
 | 
			
		||||
        model.setActiveTimeout(client.getActiveTimeout());
 | 
			
		||||
        model.setExtra(LoginHelper.CLIENT_KEY, clientId);
 | 
			
		||||
        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
 | 
			
		||||
        // 生成token
 | 
			
		||||
        LoginHelper.login(loginUser, model);
 | 
			
		||||
 | 
			
		||||
        loginService.recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
 | 
			
		||||
        loginService.recordLoginInfo(user.getUserId());
 | 
			
		||||
 | 
			
		||||
        LoginVo loginVo = new LoginVo();
 | 
			
		||||
        loginVo.setAccessToken(StpUtil.getTokenValue());
 | 
			
		||||
        loginVo.setExpireIn(StpUtil.getTokenTimeout());
 | 
			
		||||
        loginVo.setClientId(clientId);
 | 
			
		||||
        loginVo.setClientId(client.getClientId());
 | 
			
		||||
        return loginVo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -98,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) {
 | 
			
		||||
@@ -111,11 +108,8 @@ public class PasswordAuthStrategy implements IAuthStrategy {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SysUserVo loadUserByUsername(String tenantId, String username) {
 | 
			
		||||
        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
 | 
			
		||||
            .select(SysUser::getUserName, SysUser::getStatus)
 | 
			
		||||
            .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
 | 
			
		||||
            .eq(SysUser::getUserName, 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);
 | 
			
		||||
@@ -123,10 +117,7 @@ public class PasswordAuthStrategy implements IAuthStrategy {
 | 
			
		||||
            log.info("登录用户:{} 已被停用.", username);
 | 
			
		||||
            throw new UserException("user.blocked", username);
 | 
			
		||||
        }
 | 
			
		||||
        if (TenantHelper.isEnable()) {
 | 
			
		||||
            return userMapper.selectTenantUserByUserName(username, tenantId);
 | 
			
		||||
        }
 | 
			
		||||
        return userMapper.selectUserByUserName(username);
 | 
			
		||||
        return user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@ 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.model.LoginBody;
 | 
			
		||||
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;
 | 
			
		||||
@@ -17,12 +17,12 @@ import org.dromara.common.core.exception.user.UserException;
 | 
			
		||||
import org.dromara.common.core.utils.MessageUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ValidatorUtils;
 | 
			
		||||
import org.dromara.common.core.validate.auth.SmsGroup;
 | 
			
		||||
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,39 +44,34 @@ public class SmsAuthStrategy implements IAuthStrategy {
 | 
			
		||||
    private final SysUserMapper userMapper;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void validate(LoginBody loginBody) {
 | 
			
		||||
        ValidatorUtils.validate(loginBody, SmsGroup.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public LoginVo login(String clientId, LoginBody loginBody, 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();
 | 
			
		||||
        model.setDevice(client.getDeviceType());
 | 
			
		||||
        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
 | 
			
		||||
        // 例如: 后台用户30分钟过期 app用户1天过期
 | 
			
		||||
        model.setTimeout(client.getTimeout());
 | 
			
		||||
        model.setActiveTimeout(client.getActiveTimeout());
 | 
			
		||||
        model.setExtra(LoginHelper.CLIENT_KEY, clientId);
 | 
			
		||||
        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
 | 
			
		||||
        // 生成token
 | 
			
		||||
        LoginHelper.login(loginUser, model);
 | 
			
		||||
 | 
			
		||||
        loginService.recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
 | 
			
		||||
        loginService.recordLoginInfo(user.getUserId());
 | 
			
		||||
 | 
			
		||||
        LoginVo loginVo = new LoginVo();
 | 
			
		||||
        loginVo.setAccessToken(StpUtil.getTokenValue());
 | 
			
		||||
        loginVo.setExpireIn(StpUtil.getTokenTimeout());
 | 
			
		||||
        loginVo.setClientId(clientId);
 | 
			
		||||
        loginVo.setClientId(client.getClientId());
 | 
			
		||||
        return loginVo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -92,11 +87,8 @@ public class SmsAuthStrategy implements IAuthStrategy {
 | 
			
		||||
        return code.equals(smsCode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
 | 
			
		||||
        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
 | 
			
		||||
            .select(SysUser::getPhonenumber, SysUser::getStatus)
 | 
			
		||||
            .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
 | 
			
		||||
            .eq(SysUser::getPhonenumber, 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);
 | 
			
		||||
@@ -104,10 +96,7 @@ public class SmsAuthStrategy implements IAuthStrategy {
 | 
			
		||||
            log.info("登录用户:{} 已被停用.", phonenumber);
 | 
			
		||||
            throw new UserException("user.blocked", phonenumber);
 | 
			
		||||
        }
 | 
			
		||||
        if (TenantHelper.isEnable()) {
 | 
			
		||||
            return userMapper.selectTenantUserByPhonenumber(phonenumber, tenantId);
 | 
			
		||||
        }
 | 
			
		||||
        return userMapper.selectUserByPhonenumber(phonenumber);
 | 
			
		||||
        return user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,31 +2,28 @@ package org.dromara.web.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.stp.SaLoginModel;
 | 
			
		||||
import cn.dev33.satoken.stp.StpUtil;
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.map.MapUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
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.Constants;
 | 
			
		||||
import org.dromara.common.core.domain.model.LoginBody;
 | 
			
		||||
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.MessageUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StreamUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ValidatorUtils;
 | 
			
		||||
import org.dromara.common.core.validate.auth.SocialGroup;
 | 
			
		||||
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;
 | 
			
		||||
@@ -36,6 +33,9 @@ import org.dromara.web.service.IAuthStrategy;
 | 
			
		||||
import org.dromara.web.service.SysLoginService;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 第三方授权策略
 | 
			
		||||
 *
 | 
			
		||||
@@ -51,22 +51,19 @@ public class SocialAuthStrategy implements IAuthStrategy {
 | 
			
		||||
    private final SysUserMapper userMapper;
 | 
			
		||||
    private final SysLoginService loginService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void validate(LoginBody loginBody) {
 | 
			
		||||
        ValidatorUtils.validate(loginBody, SocialGroup.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 登录-第三方授权登录
 | 
			
		||||
     *
 | 
			
		||||
     * @param clientId  客户端id
 | 
			
		||||
     * @param loginBody 登录信息
 | 
			
		||||
     * @param client    客户端信息
 | 
			
		||||
     * @param body     登录信息
 | 
			
		||||
     * @param client   客户端信息
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public LoginVo login(String clientId, LoginBody loginBody, SysClient client) {
 | 
			
		||||
        AuthResponse<AuthUser> response = SocialUtils.loginAuth(loginBody, socialProperties);
 | 
			
		||||
    public LoginVo login(String body, SysClientVo client) {
 | 
			
		||||
        SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
 | 
			
		||||
        ValidatorUtils.validate(loginBody);
 | 
			
		||||
        AuthResponse<AuthUser> response = SocialUtils.loginAuth(
 | 
			
		||||
                loginBody.getSource(), loginBody.getSocialCode(),
 | 
			
		||||
                loginBody.getSocialState(), socialProperties);
 | 
			
		||||
        if (!response.ok()) {
 | 
			
		||||
            throw new ServiceException(response.getMsg());
 | 
			
		||||
        }
 | 
			
		||||
@@ -74,54 +71,53 @@ public class SocialAuthStrategy implements IAuthStrategy {
 | 
			
		||||
        if ("GITEE".equals(authUserData.getSource())) {
 | 
			
		||||
            // 如用户使用 gitee 登录顺手 star 给作者一点支持 拒绝白嫖
 | 
			
		||||
            HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Vue-Plus")
 | 
			
		||||
                .formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
 | 
			
		||||
                .executeAsync();
 | 
			
		||||
                    .formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
 | 
			
		||||
                    .executeAsync();
 | 
			
		||||
            HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Cloud-Plus")
 | 
			
		||||
                .formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
 | 
			
		||||
                .executeAsync();
 | 
			
		||||
                    .formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
 | 
			
		||||
                    .executeAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SysSocialVo social = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
 | 
			
		||||
        if (!ObjectUtil.isNotNull(social)) {
 | 
			
		||||
        List<SysSocialVo> list = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
 | 
			
		||||
        if (CollUtil.isEmpty(list)) {
 | 
			
		||||
            throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
 | 
			
		||||
        }
 | 
			
		||||
        // 验证授权表里面的租户id是否包含当前租户id
 | 
			
		||||
        String tenantId = social.getTenantId();
 | 
			
		||||
        if (ObjectUtil.isNotNull(social) && StrUtil.isNotBlank(tenantId)
 | 
			
		||||
            && !tenantId.contains(loginBody.getTenantId())) {
 | 
			
		||||
            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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 查找用户
 | 
			
		||||
        SysUserVo user = loadUser(tenantId, 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();
 | 
			
		||||
        model.setDevice(client.getDeviceType());
 | 
			
		||||
        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
 | 
			
		||||
        // 例如: 后台用户30分钟过期 app用户1天过期
 | 
			
		||||
        model.setTimeout(client.getTimeout());
 | 
			
		||||
        model.setActiveTimeout(client.getActiveTimeout());
 | 
			
		||||
        model.setExtra(LoginHelper.CLIENT_KEY, clientId);
 | 
			
		||||
        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
 | 
			
		||||
        // 生成token
 | 
			
		||||
        LoginHelper.login(loginUser, model);
 | 
			
		||||
 | 
			
		||||
        loginService.recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
 | 
			
		||||
        loginService.recordLoginInfo(user.getUserId());
 | 
			
		||||
 | 
			
		||||
        LoginVo loginVo = new LoginVo();
 | 
			
		||||
        loginVo.setAccessToken(StpUtil.getTokenValue());
 | 
			
		||||
        loginVo.setExpireIn(StpUtil.getTokenTimeout());
 | 
			
		||||
        loginVo.setClientId(clientId);
 | 
			
		||||
        loginVo.setClientId(client.getClientId());
 | 
			
		||||
        return loginVo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SysUserVo loadUser(String tenantId, Long userId) {
 | 
			
		||||
        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
 | 
			
		||||
            .select(SysUser::getUserName, SysUser::getStatus)
 | 
			
		||||
            .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
 | 
			
		||||
            .eq(SysUser::getUserId, userId));
 | 
			
		||||
    private SysUserVo loadUser(Long userId) {
 | 
			
		||||
        SysUserVo user = userMapper.selectVoById(userId);
 | 
			
		||||
        if (ObjectUtil.isNull(user)) {
 | 
			
		||||
            log.info("登录用户:{} 不存在.", "");
 | 
			
		||||
            throw new UserException("user.not.exists", "");
 | 
			
		||||
@@ -129,10 +125,7 @@ public class SocialAuthStrategy implements IAuthStrategy {
 | 
			
		||||
            log.info("登录用户:{} 已被停用.", "");
 | 
			
		||||
            throw new UserException("user.blocked", "");
 | 
			
		||||
        }
 | 
			
		||||
        if (TenantHelper.isEnable()) {
 | 
			
		||||
            return userMapper.selectTenantUserByUserName(user.getUserName(), tenantId);
 | 
			
		||||
        }
 | 
			
		||||
        return userMapper.selectUserByUserName(user.getUserName());
 | 
			
		||||
        return user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,15 +5,14 @@ import cn.dev33.satoken.stp.StpUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.core.constant.Constants;
 | 
			
		||||
import org.dromara.common.core.domain.model.LoginBody;
 | 
			
		||||
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.utils.MessageUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ValidatorUtils;
 | 
			
		||||
import org.dromara.common.core.validate.auth.WechatGroup;
 | 
			
		||||
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;
 | 
			
		||||
@@ -21,7 +20,7 @@ import org.dromara.web.service.SysLoginService;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 邮件认证策略
 | 
			
		||||
 * 小程序认证策略
 | 
			
		||||
 *
 | 
			
		||||
 * @author Michelle.Chung
 | 
			
		||||
 */
 | 
			
		||||
@@ -33,14 +32,14 @@ public class XcxAuthStrategy implements IAuthStrategy {
 | 
			
		||||
    private final SysLoginService loginService;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void validate(LoginBody loginBody) {
 | 
			
		||||
        ValidatorUtils.validate(loginBody, WechatGroup.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public LoginVo login(String clientId, LoginBody loginBody, SysClient client) {
 | 
			
		||||
    public LoginVo login(String body, SysClientVo client) {
 | 
			
		||||
        XcxLoginBody loginBody = JsonUtils.parseObject(body, XcxLoginBody.class);
 | 
			
		||||
        ValidatorUtils.validate(loginBody);
 | 
			
		||||
        // xcxCode 为 小程序调用 wx.login 授权后获取
 | 
			
		||||
        String xcxCode = loginBody.getXcxCode();
 | 
			
		||||
        // 多个小程序识别使用
 | 
			
		||||
        String appid = loginBody.getAppid();
 | 
			
		||||
 | 
			
		||||
        // todo 以下自行实现
 | 
			
		||||
        // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
 | 
			
		||||
        String openid = "";
 | 
			
		||||
@@ -54,6 +53,8 @@ public class XcxAuthStrategy implements IAuthStrategy {
 | 
			
		||||
        loginUser.setUsername(user.getUserName());
 | 
			
		||||
        loginUser.setNickname(user.getNickName());
 | 
			
		||||
        loginUser.setUserType(user.getUserType());
 | 
			
		||||
        loginUser.setClientKey(client.getClientKey());
 | 
			
		||||
        loginUser.setDeviceType(client.getDeviceType());
 | 
			
		||||
        loginUser.setOpenid(openid);
 | 
			
		||||
 | 
			
		||||
        SaLoginModel model = new SaLoginModel();
 | 
			
		||||
@@ -62,17 +63,14 @@ public class XcxAuthStrategy implements IAuthStrategy {
 | 
			
		||||
        // 例如: 后台用户30分钟过期 app用户1天过期
 | 
			
		||||
        model.setTimeout(client.getTimeout());
 | 
			
		||||
        model.setActiveTimeout(client.getActiveTimeout());
 | 
			
		||||
        model.setExtra(LoginHelper.CLIENT_KEY, clientId);
 | 
			
		||||
        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
 | 
			
		||||
        // 生成token
 | 
			
		||||
        LoginHelper.login(loginUser, model);
 | 
			
		||||
 | 
			
		||||
        loginService.recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
 | 
			
		||||
        loginService.recordLoginInfo(user.getUserId());
 | 
			
		||||
 | 
			
		||||
        LoginVo loginVo = new LoginVo();
 | 
			
		||||
        loginVo.setAccessToken(StpUtil.getTokenValue());
 | 
			
		||||
        loginVo.setExpireIn(StpUtil.getTokenTimeout());
 | 
			
		||||
        loginVo.setClientId(clientId);
 | 
			
		||||
        loginVo.setClientId(client.getClientId());
 | 
			
		||||
        loginVo.setOpenid(openid);
 | 
			
		||||
        return loginVo;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,24 +5,26 @@ spring.boot.admin.client:
 | 
			
		||||
  url: http://localhost:9090/admin
 | 
			
		||||
  instance:
 | 
			
		||||
    service-host-type: IP
 | 
			
		||||
    metadata:
 | 
			
		||||
      username: ${spring.boot.admin.client.username}
 | 
			
		||||
      userpassword: ${spring.boot.admin.client.password}
 | 
			
		||||
  username: ruoyi
 | 
			
		||||
  password: 123456
 | 
			
		||||
 | 
			
		||||
--- # powerjob 配置
 | 
			
		||||
powerjob:
 | 
			
		||||
  worker:
 | 
			
		||||
    # 如何开启调度中心请查看文档教程
 | 
			
		||||
    enabled: false
 | 
			
		||||
    # 需要先在 powerjob 登录页执行应用注册后才能使用
 | 
			
		||||
    app-name: ruoyi-worker
 | 
			
		||||
    enable-test-mode: 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/snail_job.sql `sj_group_config` 表
 | 
			
		||||
  token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
 | 
			
		||||
  server:
 | 
			
		||||
    host: 127.0.0.1
 | 
			
		||||
    port: 17888
 | 
			
		||||
  # 详见 script/sql/snail_job.sql `sj_namespace` 表
 | 
			
		||||
  namespace: ${spring.profiles.active}
 | 
			
		||||
  # 随主应用端口飘逸
 | 
			
		||||
  port: 2${server.port}
 | 
			
		||||
 | 
			
		||||
--- # 数据源配置
 | 
			
		||||
spring:
 | 
			
		||||
@@ -43,7 +45,7 @@ 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
 | 
			
		||||
        # 从库数据源
 | 
			
		||||
@@ -51,7 +53,7 @@ spring:
 | 
			
		||||
          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
 | 
			
		||||
          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:
 | 
			
		||||
@@ -60,8 +62,6 @@ spring:
 | 
			
		||||
#          url: jdbc:oracle:thin:@//localhost:1521/XE
 | 
			
		||||
#          username: ROOT
 | 
			
		||||
#          password: root
 | 
			
		||||
#          hikari:
 | 
			
		||||
#            connectionTestQuery: SELECT 1 FROM DUAL
 | 
			
		||||
#        postgres:
 | 
			
		||||
#          type: ${spring.datasource.type}
 | 
			
		||||
#          driverClassName: org.postgresql.Driver
 | 
			
		||||
@@ -87,8 +87,6 @@ spring:
 | 
			
		||||
        idleTimeout: 600000
 | 
			
		||||
        # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
 | 
			
		||||
        maxLifetime: 1800000
 | 
			
		||||
        # 连接测试query(配置检测连接是否有效)
 | 
			
		||||
        connectionTestQuery: SELECT 1
 | 
			
		||||
        # 多久检查一次连接的活性
 | 
			
		||||
        keepaliveTime: 30000
 | 
			
		||||
 | 
			
		||||
@@ -101,13 +99,14 @@ spring.data:
 | 
			
		||||
    port: 6379
 | 
			
		||||
    # 数据库索引
 | 
			
		||||
    database: 0
 | 
			
		||||
    # 密码(如没有密码请注释掉)
 | 
			
		||||
    # password:
 | 
			
		||||
    # redis 密码必须配置
 | 
			
		||||
    password: ruoyi123
 | 
			
		||||
    # 连接超时时间
 | 
			
		||||
    timeout: 10s
 | 
			
		||||
    # 是否开启ssl
 | 
			
		||||
    ssl.enabled: false
 | 
			
		||||
 | 
			
		||||
# redisson 配置
 | 
			
		||||
redisson:
 | 
			
		||||
  # redis key前缀
 | 
			
		||||
  keyPrefix:
 | 
			
		||||
@@ -153,36 +152,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:
 | 
			
		||||
@@ -193,6 +196,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
 | 
			
		||||
@@ -227,10 +237,10 @@ justauth:
 | 
			
		||||
      client-id: 10**********6
 | 
			
		||||
      client-secret: 1f7d08**********5b7**********29e
 | 
			
		||||
      redirect-uri: ${justauth.address}/social-callback?source=oschina
 | 
			
		||||
    alipay:
 | 
			
		||||
    alipay_wallet:
 | 
			
		||||
      client-id: 10**********6
 | 
			
		||||
      client-secret: 1f7d08**********5b7**********29e
 | 
			
		||||
      redirect-uri: ${justauth.address}/social-callback?source=alipay
 | 
			
		||||
      redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
 | 
			
		||||
      alipay-public-key: MIIB**************DAQAB
 | 
			
		||||
    wechat_open:
 | 
			
		||||
      client-id: 10**********6
 | 
			
		||||
 
 | 
			
		||||
@@ -8,24 +8,26 @@ spring.boot.admin.client:
 | 
			
		||||
  url: http://localhost:9090/admin
 | 
			
		||||
  instance:
 | 
			
		||||
    service-host-type: IP
 | 
			
		||||
    metadata:
 | 
			
		||||
      username: ${spring.boot.admin.client.username}
 | 
			
		||||
      userpassword: ${spring.boot.admin.client.password}
 | 
			
		||||
  username: ruoyi
 | 
			
		||||
  password: 123456
 | 
			
		||||
 | 
			
		||||
--- # powerjob 配置
 | 
			
		||||
powerjob:
 | 
			
		||||
  worker:
 | 
			
		||||
    # 如何开启调度中心请查看文档教程
 | 
			
		||||
    enabled: false
 | 
			
		||||
    # 需要先在 powerjob 登录页执行应用注册后才能使用
 | 
			
		||||
    app-name: ruoyi-worker
 | 
			
		||||
    enable-test-mode: 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/snail_job.sql `sj_group_config` 表
 | 
			
		||||
  token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
 | 
			
		||||
  server:
 | 
			
		||||
    host: 127.0.0.1
 | 
			
		||||
    port: 17888
 | 
			
		||||
  # 详见 script/sql/snail_job.sql `sj_namespace` 表
 | 
			
		||||
  namespace: ${spring.profiles.active}
 | 
			
		||||
  # 随主应用端口飘逸
 | 
			
		||||
  port: 2${server.port}
 | 
			
		||||
 | 
			
		||||
--- # 数据源配置
 | 
			
		||||
spring:
 | 
			
		||||
@@ -46,7 +48,7 @@ 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
 | 
			
		||||
        # 从库数据源
 | 
			
		||||
@@ -54,7 +56,7 @@ spring:
 | 
			
		||||
          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
 | 
			
		||||
          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:
 | 
			
		||||
@@ -63,8 +65,6 @@ spring:
 | 
			
		||||
#          url: jdbc:oracle:thin:@//localhost:1521/XE
 | 
			
		||||
#          username: ROOT
 | 
			
		||||
#          password: root
 | 
			
		||||
#          hikari:
 | 
			
		||||
#            connectionTestQuery: SELECT 1 FROM DUAL
 | 
			
		||||
#        postgres:
 | 
			
		||||
#          type: ${spring.datasource.type}
 | 
			
		||||
#          driverClassName: org.postgresql.Driver
 | 
			
		||||
@@ -90,8 +90,6 @@ spring:
 | 
			
		||||
        idleTimeout: 600000
 | 
			
		||||
        # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
 | 
			
		||||
        maxLifetime: 1800000
 | 
			
		||||
        # 连接测试query(配置检测连接是否有效)
 | 
			
		||||
        connectionTestQuery: SELECT 1
 | 
			
		||||
        # 多久检查一次连接的活性
 | 
			
		||||
        keepaliveTime: 30000
 | 
			
		||||
 | 
			
		||||
@@ -104,13 +102,14 @@ spring.data:
 | 
			
		||||
    port: 6379
 | 
			
		||||
    # 数据库索引
 | 
			
		||||
    database: 0
 | 
			
		||||
    # 密码(如没有密码请注释掉)
 | 
			
		||||
    # password:
 | 
			
		||||
    # redis 密码必须配置
 | 
			
		||||
    password: ruoyi123
 | 
			
		||||
    # 连接超时时间
 | 
			
		||||
    timeout: 10s
 | 
			
		||||
    # 是否开启ssl
 | 
			
		||||
    ssl.enabled: false
 | 
			
		||||
 | 
			
		||||
# redisson 配置
 | 
			
		||||
redisson:
 | 
			
		||||
  # redis key前缀
 | 
			
		||||
  keyPrefix:
 | 
			
		||||
@@ -156,35 +155,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:
 | 
			
		||||
@@ -195,6 +198,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
 | 
			
		||||
@@ -229,10 +239,10 @@ justauth:
 | 
			
		||||
      client-id: 10**********6
 | 
			
		||||
      client-secret: 1f7d08**********5b7**********29e
 | 
			
		||||
      redirect-uri: ${justauth.address}/social-callback?source=oschina
 | 
			
		||||
    alipay:
 | 
			
		||||
    alipay_wallet:
 | 
			
		||||
      client-id: 10**********6
 | 
			
		||||
      client-secret: 1f7d08**********5b7**********29e
 | 
			
		||||
      redirect-uri: ${justauth.address}/social-callback?source=alipay
 | 
			
		||||
      redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
 | 
			
		||||
      alipay-public-key: MIIB**************DAQAB
 | 
			
		||||
    wechat_open:
 | 
			
		||||
      client-id: 10**********6
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ ruoyi:
 | 
			
		||||
  # 版本
 | 
			
		||||
  version: ${revision}
 | 
			
		||||
  # 版权年份
 | 
			
		||||
  copyrightYear: 2023
 | 
			
		||||
  copyrightYear: 2024
 | 
			
		||||
 | 
			
		||||
captcha:
 | 
			
		||||
  enable: true
 | 
			
		||||
@@ -46,7 +46,7 @@ logging:
 | 
			
		||||
  level:
 | 
			
		||||
    org.dromara: @logging.level@
 | 
			
		||||
    org.springframework: warn
 | 
			
		||||
    tech.powerjob.worker.background: warn
 | 
			
		||||
    org.mybatis.spring.mapper: error
 | 
			
		||||
  config: classpath:logback-plus.xml
 | 
			
		||||
 | 
			
		||||
# 用户配置
 | 
			
		||||
@@ -61,6 +61,10 @@ user:
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: ${ruoyi.name}
 | 
			
		||||
  threads:
 | 
			
		||||
    # 开启虚拟线程 仅jdk21可用
 | 
			
		||||
    virtual:
 | 
			
		||||
      enabled: false
 | 
			
		||||
  # 资源信息
 | 
			
		||||
  messages:
 | 
			
		||||
    # 国际化资源文件路径
 | 
			
		||||
@@ -75,6 +79,8 @@ spring:
 | 
			
		||||
      # 设置总上传的文件大小
 | 
			
		||||
      max-request-size: 20MB
 | 
			
		||||
  mvc:
 | 
			
		||||
    # 设置静态资源路径 防止所有请求都去查静态资源
 | 
			
		||||
    static-path-pattern: /static/**
 | 
			
		||||
    format:
 | 
			
		||||
      date-time: yyyy-MM-dd HH:mm:ss
 | 
			
		||||
  jackson:
 | 
			
		||||
@@ -93,11 +99,6 @@ spring:
 | 
			
		||||
sa-token:
 | 
			
		||||
  # token名称 (同时也是cookie名称)
 | 
			
		||||
  token-name: Authorization
 | 
			
		||||
  # token固定超时 设为七天 (必定过期) 单位: 秒
 | 
			
		||||
  timeout: 604800
 | 
			
		||||
  # 多端不同 token 有效期 可查看 LoginHelper.loginByDevice 方法自定义
 | 
			
		||||
  # token最低活跃时间 (指定时间无操作就过期) 单位: 秒
 | 
			
		||||
  active-timeout: 1800
 | 
			
		||||
  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
 | 
			
		||||
  is-concurrent: true
 | 
			
		||||
  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
 | 
			
		||||
@@ -120,9 +121,6 @@ security:
 | 
			
		||||
    # swagger 文档配置
 | 
			
		||||
    - /*/api-docs
 | 
			
		||||
    - /*/api-docs/**
 | 
			
		||||
    # actuator 监控配置
 | 
			
		||||
    - /actuator
 | 
			
		||||
    - /actuator/**
 | 
			
		||||
 | 
			
		||||
# 多租户配置
 | 
			
		||||
tenant:
 | 
			
		||||
@@ -138,12 +136,12 @@ tenant:
 | 
			
		||||
    - sys_user_post
 | 
			
		||||
    - sys_user_role
 | 
			
		||||
    - sys_client
 | 
			
		||||
    - sys_oss_config
 | 
			
		||||
 | 
			
		||||
# MyBatisPlus配置
 | 
			
		||||
# https://baomidou.com/config/
 | 
			
		||||
mybatis-plus:
 | 
			
		||||
  # 不支持多包, 如有需要可在注解配置 或 提升扫包等级
 | 
			
		||||
  # 例如 com.**.**.mapper
 | 
			
		||||
  # 多包名使用 例如 org.dromara.**.mapper,org.xxx.**.mapper
 | 
			
		||||
  mapperPackage: org.dromara.**.mapper
 | 
			
		||||
  # 对应的 XML 文件位置
 | 
			
		||||
  mapperLocations: classpath*:mapper/**/*Mapper.xml
 | 
			
		||||
@@ -176,8 +174,11 @@ api-decrypt:
 | 
			
		||||
  enabled: true
 | 
			
		||||
  # AES 加密头标识
 | 
			
		||||
  headerFlag: encrypt-key
 | 
			
		||||
  # 公私钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换
 | 
			
		||||
  publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
 | 
			
		||||
  # 响应加密公钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换
 | 
			
		||||
  # 对应前端解密私钥 MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
 | 
			
		||||
  publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJnNwrj4hi/y3CCJu868ghCG5dUj8wZK++RNlTLcXoMmdZWEQ/u02RgD5LyLAXGjLOjbMtC+/J9qofpSGTKSx/MCAwEAAQ==
 | 
			
		||||
  # 请求解密私钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换
 | 
			
		||||
  # 对应前端加密公钥 MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
 | 
			
		||||
  privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y=
 | 
			
		||||
 | 
			
		||||
springdoc:
 | 
			
		||||
@@ -227,6 +228,7 @@ xss:
 | 
			
		||||
  urlPatterns: /system/*,/monitor/*,/tool/*
 | 
			
		||||
 | 
			
		||||
# 全局线程池相关配置
 | 
			
		||||
# 如使用JDK21请直接使用虚拟线程 不要开启此配置
 | 
			
		||||
thread-pool:
 | 
			
		||||
  # 是否开启线程池
 | 
			
		||||
  enabled: false
 | 
			
		||||
@@ -254,10 +256,38 @@ 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: '*'
 | 
			
		||||
 | 
			
		||||
--- #flowable配置
 | 
			
		||||
flowable:
 | 
			
		||||
  # 开关 用于启动/停用工作流
 | 
			
		||||
  enabled: true
 | 
			
		||||
  process.enabled: ${flowable.enabled}
 | 
			
		||||
  eventregistry.enabled: ${flowable.enabled}
 | 
			
		||||
  async-executor-activate: false #关闭定时任务JOB
 | 
			
		||||
  #  将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
 | 
			
		||||
  database-schema-update: true
 | 
			
		||||
  activity-font-name: 宋体
 | 
			
		||||
  label-font-name: 宋体
 | 
			
		||||
  annotation-font-name: 宋体
 | 
			
		||||
  # 关闭各个模块生成表,目前只使用工作流基础表
 | 
			
		||||
  idm:
 | 
			
		||||
    enabled: false
 | 
			
		||||
  cmmn:
 | 
			
		||||
    enabled: false
 | 
			
		||||
  dmn:
 | 
			
		||||
    enabled: false
 | 
			
		||||
  app:
 | 
			
		||||
    enabled: false
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ user.notfound=请重新登录
 | 
			
		||||
user.forcelogout=管理员强制退出,请重新登录
 | 
			
		||||
user.unknown.error=未知错误,请重新登录
 | 
			
		||||
auth.grant.type.error=认证权限类型错误
 | 
			
		||||
auth.grant.type.blocked=认证权限类型已禁用
 | 
			
		||||
auth.grant.type.not.blank=认证权限类型不能为空
 | 
			
		||||
auth.clientid.not.blank=认证客户端id不能为空
 | 
			
		||||
##文件上传消息
 | 
			
		||||
@@ -49,7 +50,10 @@ sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}
 | 
			
		||||
email.code.not.blank=邮箱验证码不能为空
 | 
			
		||||
email.code.retry.limit.count=邮箱验证码输入错误{0}次
 | 
			
		||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
 | 
			
		||||
xcx.code.not.blank=小程序code不能为空
 | 
			
		||||
xcx.code.not.blank=小程序[code]不能为空
 | 
			
		||||
social.source.not.blank=第三方登录平台[source]不能为空
 | 
			
		||||
social.code.not.blank=第三方登录平台[code]不能为空
 | 
			
		||||
social.state.not.blank=第三方登录平台[state]不能为空
 | 
			
		||||
##租户
 | 
			
		||||
tenant.number.not.blank=租户编号不能为空
 | 
			
		||||
tenant.not.exists=对不起, 您的租户不存在,请联系管理员
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ user.notfound=Please login again
 | 
			
		||||
user.forcelogout=The administrator is forced to exit,please login again
 | 
			
		||||
user.unknown.error=Unknown error, please login again
 | 
			
		||||
auth.grant.type.error=Auth grant type error
 | 
			
		||||
auth.grant.type.blocked=Auth grant type disabled
 | 
			
		||||
auth.grant.type.not.blank=Auth grant type cannot be blank
 | 
			
		||||
auth.clientid.not.blank=Auth clientid cannot be blank
 | 
			
		||||
##文件上传消息
 | 
			
		||||
@@ -49,7 +50,10 @@ sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {
 | 
			
		||||
email.code.not.blank=Email code cannot be blank
 | 
			
		||||
email.code.retry.limit.count=Email code input error {0} times
 | 
			
		||||
email.code.retry.limit.exceed=Email code input error {0} times, account locked for {1} minutes
 | 
			
		||||
xcx.code.not.blank=Mini program code cannot be blank
 | 
			
		||||
xcx.code.not.blank=Mini program [code] cannot be blank
 | 
			
		||||
social.source.not.blank=Social login platform [source] cannot be blank
 | 
			
		||||
social.code.not.blank=Social login platform [code] cannot be blank
 | 
			
		||||
social.state.not.blank=Social login platform [state] cannot be blank
 | 
			
		||||
##租户
 | 
			
		||||
tenant.number.not.blank=Tenant number cannot be blank
 | 
			
		||||
tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ user.notfound=请重新登录
 | 
			
		||||
user.forcelogout=管理员强制退出,请重新登录
 | 
			
		||||
user.unknown.error=未知错误,请重新登录
 | 
			
		||||
auth.grant.type.error=认证权限类型错误
 | 
			
		||||
auth.grant.type.blocked=认证权限类型已禁用
 | 
			
		||||
auth.grant.type.not.blank=认证权限类型不能为空
 | 
			
		||||
auth.clientid.not.blank=认证客户端id不能为空
 | 
			
		||||
##文件上传消息
 | 
			
		||||
@@ -49,7 +50,10 @@ sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}
 | 
			
		||||
email.code.not.blank=邮箱验证码不能为空
 | 
			
		||||
email.code.retry.limit.count=邮箱验证码输入错误{0}次
 | 
			
		||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
 | 
			
		||||
xcx.code.not.blank=小程序code不能为空
 | 
			
		||||
xcx.code.not.blank=小程序[code]不能为空
 | 
			
		||||
social.source.not.blank=第三方登录平台[source]不能为空
 | 
			
		||||
social.code.not.blank=第三方登录平台[code]不能为空
 | 
			
		||||
social.state.not.blank=第三方登录平台[state]不能为空
 | 
			
		||||
##租户
 | 
			
		||||
tenant.number.not.blank=租户编号不能为空
 | 
			
		||||
tenant.not.exists=对不起, 您的租户不存在,请联系管理员
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -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.0</revision>
 | 
			
		||||
        <revision>5.2.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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -66,12 +66,6 @@
 | 
			
		||||
            <artifactId>hutool-extra</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>cn.hutool</groupId>
 | 
			
		||||
            <artifactId>hutool-json</artifactId>
 | 
			
		||||
            <scope>provided</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.projectlombok</groupId>
 | 
			
		||||
            <artifactId>lombok</artifactId>
 | 
			
		||||
 
 | 
			
		||||
@@ -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");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,4 +22,9 @@ public interface CacheConstants {
 | 
			
		||||
     */
 | 
			
		||||
    String SYS_DICT_KEY = "sys_dict:";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 登录账户密码错误次数 redis key
 | 
			
		||||
     */
 | 
			
		||||
    String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -35,11 +35,21 @@ public interface CacheNames {
 | 
			
		||||
     */
 | 
			
		||||
    String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 客户端
 | 
			
		||||
     */
 | 
			
		||||
    String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户账户
 | 
			
		||||
     */
 | 
			
		||||
    String SYS_USER_NAME = "sys_user_name#30d";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户名称
 | 
			
		||||
     */
 | 
			
		||||
    String SYS_NICKNAME = "sys_nickname#30d";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 部门
 | 
			
		||||
     */
 | 
			
		||||
@@ -53,7 +63,7 @@ public interface CacheNames {
 | 
			
		||||
    /**
 | 
			
		||||
     * OSS配置
 | 
			
		||||
     */
 | 
			
		||||
    String SYS_OSS_CONFIG = "sys_oss_config";
 | 
			
		||||
    String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 在线用户
 | 
			
		||||
 
 | 
			
		||||
@@ -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,54 @@
 | 
			
		||||
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_]*$";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 权限标识必须符合 tool:build:list 格式,或者空字符串
 | 
			
		||||
     */
 | 
			
		||||
    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]$";
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -67,6 +67,16 @@ public interface UserConstants {
 | 
			
		||||
     */
 | 
			
		||||
    String DICT_NORMAL = "0";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 通用存在标志
 | 
			
		||||
     */
 | 
			
		||||
    String DEL_FLAG_NORMAL = "0";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 通用删除标志
 | 
			
		||||
     */
 | 
			
		||||
    String DEL_FLAG_REMOVED  = "2";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否为系统默认(是)
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -34,6 +34,16 @@ public class UserOnlineDTO implements Serializable {
 | 
			
		||||
     */
 | 
			
		||||
    private String userName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 客户端
 | 
			
		||||
     */
 | 
			
		||||
    private String clientKey;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设备类型
 | 
			
		||||
     */
 | 
			
		||||
    private String deviceType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 登录IP地址
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
package org.dromara.common.core.domain.event;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 总体流程监听
 | 
			
		||||
 *
 | 
			
		||||
 * @author may
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class ProcessEvent implements Serializable {
 | 
			
		||||
 | 
			
		||||
    @Serial
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程定义key
 | 
			
		||||
     */
 | 
			
		||||
    private String key;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 业务id
 | 
			
		||||
     */
 | 
			
		||||
    private String businessKey;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 状态
 | 
			
		||||
     */
 | 
			
		||||
    private String status;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 当为true时为申请人节点办理
 | 
			
		||||
     */
 | 
			
		||||
    private boolean submit;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,40 @@
 | 
			
		||||
package org.dromara.common.core.domain.event;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 流程办理监听
 | 
			
		||||
 *
 | 
			
		||||
 * @author may
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class ProcessTaskEvent implements Serializable {
 | 
			
		||||
 | 
			
		||||
    @Serial
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程定义key
 | 
			
		||||
     */
 | 
			
		||||
    private String key;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 审批节点key
 | 
			
		||||
     */
 | 
			
		||||
    private String taskDefinitionKey;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 任务id
 | 
			
		||||
     */
 | 
			
		||||
    private String taskId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 业务id
 | 
			
		||||
     */
 | 
			
		||||
    private String businessKey;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -3,6 +3,7 @@ package org.dromara.common.core.domain.model;
 | 
			
		||||
import jakarta.validation.constraints.Email;
 | 
			
		||||
import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 邮件登录对象
 | 
			
		||||
@@ -11,13 +12,8 @@ import lombok.Data;
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class EmailLoginBody {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 租户ID
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{tenant.number.not.blank}")
 | 
			
		||||
    private String tenantId;
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
public class EmailLoginBody extends LoginBody {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 邮箱
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,10 @@
 | 
			
		||||
package org.dromara.common.core.domain.model;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.constraints.Email;
 | 
			
		||||
import org.dromara.common.core.constant.UserConstants;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.dromara.common.core.validate.auth.*;
 | 
			
		||||
import org.hibernate.validator.constraints.Length;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 用户登录对象
 | 
			
		||||
@@ -15,7 +13,10 @@ import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class LoginBody {
 | 
			
		||||
public class LoginBody implements Serializable {
 | 
			
		||||
 | 
			
		||||
    @Serial
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 客户端id
 | 
			
		||||
@@ -23,16 +24,6 @@ public class LoginBody {
 | 
			
		||||
    @NotBlank(message = "{auth.clientid.not.blank}")
 | 
			
		||||
    private String clientId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 客户端key
 | 
			
		||||
     */
 | 
			
		||||
    private String clientKey;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 客户端秘钥
 | 
			
		||||
     */
 | 
			
		||||
    private String clientSecret;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 授权类型
 | 
			
		||||
     */
 | 
			
		||||
@@ -42,23 +33,8 @@ public class LoginBody {
 | 
			
		||||
    /**
 | 
			
		||||
     * 租户ID
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{tenant.number.not.blank}")
 | 
			
		||||
    private String tenantId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户名
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{user.username.not.blank}", groups = {PasswordGroup.class})
 | 
			
		||||
    @Length(min = UserConstants.USERNAME_MIN_LENGTH, max = UserConstants.USERNAME_MAX_LENGTH, message = "{user.username.length.valid}", groups = {PasswordGroup.class})
 | 
			
		||||
    private String username;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户密码
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{user.password.not.blank}", groups = {PasswordGroup.class})
 | 
			
		||||
    @Length(min = UserConstants.PASSWORD_MIN_LENGTH, max = UserConstants.PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}", groups = {PasswordGroup.class})
 | 
			
		||||
    private String password;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 验证码
 | 
			
		||||
     */
 | 
			
		||||
@@ -69,52 +45,4 @@ public class LoginBody {
 | 
			
		||||
     */
 | 
			
		||||
    private String uuid;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 手机号
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{user.phonenumber.not.blank}", groups = {SmsGroup.class})
 | 
			
		||||
    private String phonenumber;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 短信code
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{sms.code.not.blank}", groups = {SmsGroup.class})
 | 
			
		||||
    private String smsCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 邮箱
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{user.email.not.blank}", groups = {EmailGroup.class})
 | 
			
		||||
    @Email(message = "{user.email.not.valid}")
 | 
			
		||||
    private String email;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 邮箱code
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{email.code.not.blank}", groups = {EmailGroup.class})
 | 
			
		||||
    private String emailCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 小程序code
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{xcx.code.not.blank}", groups = {WechatGroup.class})
 | 
			
		||||
    private String xcxCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 第三方登录平台
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{social.source.not.blank}" , groups = {SocialGroup.class})
 | 
			
		||||
    private String source;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 第三方登录code
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{social.code.not.blank}" , groups = {SocialGroup.class})
 | 
			
		||||
    private String socialCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 第三方登录socialState
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{social.state.not.blank}" , groups = {SocialGroup.class})
 | 
			
		||||
    private String socialState;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@ import java.util.Set;
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
public class LoginUser implements Serializable {
 | 
			
		||||
@@ -37,6 +36,11 @@ public class LoginUser implements Serializable {
 | 
			
		||||
     */
 | 
			
		||||
    private Long deptId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 部门类别编码
 | 
			
		||||
     */
 | 
			
		||||
    private String deptCategory;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 部门名
 | 
			
		||||
     */
 | 
			
		||||
@@ -112,6 +116,16 @@ public class LoginUser implements Serializable {
 | 
			
		||||
     */
 | 
			
		||||
    private Long roleId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 客户端
 | 
			
		||||
     */
 | 
			
		||||
    private String clientKey;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设备类型
 | 
			
		||||
     */
 | 
			
		||||
    private String deviceType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取登录id
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
package org.dromara.common.core.domain.model;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import org.hibernate.validator.constraints.Length;
 | 
			
		||||
 | 
			
		||||
import static org.dromara.common.core.constant.UserConstants.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 密码登录对象
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
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}")
 | 
			
		||||
    private String username;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户密码
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{user.password.not.blank}")
 | 
			
		||||
    @Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
 | 
			
		||||
    private String password;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,11 @@
 | 
			
		||||
package org.dromara.common.core.domain.model;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import org.hibernate.validator.constraints.Length;
 | 
			
		||||
 | 
			
		||||
import static org.dromara.common.core.constant.UserConstants.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 用户注册对象
 | 
			
		||||
@@ -12,6 +16,20 @@ import lombok.EqualsAndHashCode;
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
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}")
 | 
			
		||||
    private String username;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户密码
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{user.password.not.blank}")
 | 
			
		||||
    @Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
 | 
			
		||||
    private String password;
 | 
			
		||||
 | 
			
		||||
    private String userType;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
package org.dromara.common.core.domain.model;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 短信登录对象
 | 
			
		||||
@@ -11,13 +11,8 @@ import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class SmsLoginBody {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 租户ID
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{tenant.number.not.blank}")
 | 
			
		||||
    private String tenantId;
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
public class SmsLoginBody extends LoginBody {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 手机号
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
package org.dromara.common.core.domain.model;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 第三方登录用户身份权限
 | 
			
		||||
 *
 | 
			
		||||
 * @author thiszhc is 三三
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
public class SocialLogin extends LoginUser{
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * openid
 | 
			
		||||
     */
 | 
			
		||||
    private String openid;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,35 @@
 | 
			
		||||
package org.dromara.common.core.domain.model;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 三方登录对象
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
public class SocialLoginBody extends LoginBody {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 第三方登录平台
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{social.source.not.blank}")
 | 
			
		||||
    private String source;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 第三方登录code
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{social.code.not.blank}")
 | 
			
		||||
    private String socialCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 第三方登录socialState
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{social.state.not.blank}")
 | 
			
		||||
    private String socialState;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
package org.dromara.common.core.domain.model;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 三方登录对象
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
public class XcxLoginBody extends LoginBody {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 小程序id(多个小程序时使用)
 | 
			
		||||
     */
 | 
			
		||||
    private String appid;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 小程序code
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{xcx.code.not.blank}")
 | 
			
		||||
    private String xcxCode;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,152 @@
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 业务状态枚举
 | 
			
		||||
 *
 | 
			
		||||
 * @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;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取业务状态
 | 
			
		||||
     *
 | 
			
		||||
     * @param status 状态
 | 
			
		||||
     */
 | 
			
		||||
    public static String findByStatus(String status) {
 | 
			
		||||
        if (StringUtils.isBlank(status)) {
 | 
			
		||||
            return StrUtil.EMPTY;
 | 
			
		||||
        }
 | 
			
		||||
        return Arrays.stream(BusinessStatusEnum.values())
 | 
			
		||||
            .filter(statusEnum -> statusEnum.getStatus().equals(status))
 | 
			
		||||
            .findFirst()
 | 
			
		||||
            .map(BusinessStatusEnum::getDesc)
 | 
			
		||||
            .orElse(StrUtil.EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 启动流程校验
 | 
			
		||||
     *
 | 
			
		||||
     * @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("流程状态为空!");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
package org.dromara.common.core.exception;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 演示模式异常
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
public class DemoModeException extends RuntimeException {
 | 
			
		||||
 | 
			
		||||
    @Serial
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    public DemoModeException() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
package org.dromara.common.core.exception;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 全局异常
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class GlobalException extends RuntimeException {
 | 
			
		||||
 | 
			
		||||
    @Serial
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 错误提示
 | 
			
		||||
     */
 | 
			
		||||
    private String message;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 错误明细,内部调试错误
 | 
			
		||||
     */
 | 
			
		||||
    private String detailMessage;
 | 
			
		||||
 | 
			
		||||
    public GlobalException(String message) {
 | 
			
		||||
        this.message = message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getDetailMessage() {
 | 
			
		||||
        return detailMessage;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public GlobalException setDetailMessage(String detailMessage) {
 | 
			
		||||
        this.detailMessage = detailMessage;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getMessage() {
 | 
			
		||||
        return message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public GlobalException setMessage(String message) {
 | 
			
		||||
        this.message = message;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
package org.dromara.common.core.exception;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 工具类异常
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
public class UtilException extends RuntimeException {
 | 
			
		||||
 | 
			
		||||
    @Serial
 | 
			
		||||
    private static final long serialVersionUID = 8247610319171014183L;
 | 
			
		||||
 | 
			
		||||
    public UtilException(Throwable e) {
 | 
			
		||||
        super(e.getMessage(), e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public UtilException(String message) {
 | 
			
		||||
        super(message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public UtilException(String message, Throwable throwable) {
 | 
			
		||||
        super(message, throwable);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
package org.dromara.common.core.exception.user;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 用户密码不正确或不符合规范异常类
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
public class UserPasswordNotMatchException extends UserException {
 | 
			
		||||
 | 
			
		||||
    @Serial
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    public UserPasswordNotMatchException() {
 | 
			
		||||
        super("user.password.not.match");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
package org.dromara.common.core.exception.user;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 用户错误最大次数异常类
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
public class UserPasswordRetryLimitExceedException extends UserException {
 | 
			
		||||
 | 
			
		||||
    @Serial
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime) {
 | 
			
		||||
        super("user.password.retry.limit.exceed", retryLimitCount, lockTime);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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.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);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,9 @@
 | 
			
		||||
package org.dromara.common.core.service;
 | 
			
		||||
 | 
			
		||||
import org.dromara.common.core.domain.dto.UserDTO;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 通用 用户服务
 | 
			
		||||
 *
 | 
			
		||||
@@ -15,4 +19,51 @@ public interface UserService {
 | 
			
		||||
     */
 | 
			
		||||
    String selectUserNameById(Long userId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 通过用户ID查询用户账户
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId 用户ID
 | 
			
		||||
     * @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);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,76 @@
 | 
			
		||||
package org.dromara.common.core.service;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 通用 工作流服务
 | 
			
		||||
 *
 | 
			
		||||
 * @author may
 | 
			
		||||
 */
 | 
			
		||||
public interface WorkflowService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息
 | 
			
		||||
     *
 | 
			
		||||
     * @param businessKeys 业务id
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    boolean deleteRunAndHisInstance(List<String> businessKeys);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取当前流程状态
 | 
			
		||||
     *
 | 
			
		||||
     * @param taskId 任务id
 | 
			
		||||
     */
 | 
			
		||||
    String getBusinessStatusByTaskId(String taskId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取当前流程状态
 | 
			
		||||
     *
 | 
			
		||||
     * @param businessKey 业务id
 | 
			
		||||
     */
 | 
			
		||||
    String getBusinessStatus(String businessKey);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置流程变量(全局变量)
 | 
			
		||||
     *
 | 
			
		||||
     * @param taskId       任务id
 | 
			
		||||
     * @param variableName 变量名称
 | 
			
		||||
     * @param value        变量值
 | 
			
		||||
     */
 | 
			
		||||
    void setVariable(String taskId, String variableName, Object value);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置流程变量(全局变量)
 | 
			
		||||
     *
 | 
			
		||||
     * @param taskId    任务id
 | 
			
		||||
     * @param variables 流程变量
 | 
			
		||||
     */
 | 
			
		||||
    void setVariables(String taskId, Map<String, Object> variables);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置流程变量(本地变量,非全局变量)
 | 
			
		||||
     *
 | 
			
		||||
     * @param taskId       任务id
 | 
			
		||||
     * @param variableName 变量名称
 | 
			
		||||
     * @param value        变量值
 | 
			
		||||
     */
 | 
			
		||||
    void setVariableLocal(String taskId, String variableName, Object value);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置流程变量(本地变量,非全局变量)
 | 
			
		||||
     *
 | 
			
		||||
     * @param taskId    任务id
 | 
			
		||||
     * @param variables 流程变量
 | 
			
		||||
     */
 | 
			
		||||
    void setVariablesLocal(String taskId, Map<String, Object> variables);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 按照业务id查询流程实例id
 | 
			
		||||
     *
 | 
			
		||||
     * @param businessKey 业务id
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    String getInstanceIdByBusinessKey(String businessKey);
 | 
			
		||||
}
 | 
			
		||||
@@ -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拼接
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,8 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
 | 
			
		||||
 | 
			
		||||
    public static final String SEPARATOR = ",";
 | 
			
		||||
 | 
			
		||||
    public static final String SLASH = "/";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取参数不为空值
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -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()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ public class AddressUtils {
 | 
			
		||||
            return UNKNOWN;
 | 
			
		||||
        }
 | 
			
		||||
        // 内网不查询
 | 
			
		||||
        ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
 | 
			
		||||
        ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
 | 
			
		||||
        if (NetUtil.isInnerIP(ip)) {
 | 
			
		||||
            return "内网IP";
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
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 {
 | 
			
		||||
            return ReUtil.get(regex, input, 1);
 | 
			
		||||
        } 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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
package org.dromara.common.core.utils.sql;
 | 
			
		||||
 | 
			
		||||
import org.dromara.common.core.exception.UtilException;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import lombok.AccessLevel;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * sql操作工具类
 | 
			
		||||
@@ -28,7 +27,7 @@ public class SqlUtil {
 | 
			
		||||
     */
 | 
			
		||||
    public static String escapeOrderBySql(String value) {
 | 
			
		||||
        if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
 | 
			
		||||
            throw new UtilException("参数不符合规范,不能进行查询");
 | 
			
		||||
            throw new IllegalArgumentException("参数不符合规范,不能进行查询");
 | 
			
		||||
        }
 | 
			
		||||
        return value;
 | 
			
		||||
    }
 | 
			
		||||
@@ -50,7 +49,7 @@ public class SqlUtil {
 | 
			
		||||
        String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
 | 
			
		||||
        for (String sqlKeyword : sqlKeywords) {
 | 
			
		||||
            if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) {
 | 
			
		||||
                throw new UtilException("参数存在SQL注入风险");
 | 
			
		||||
                throw new IllegalArgumentException("参数存在SQL注入风险");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
package org.dromara.common.core.validate.auth;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author Michelle.Chung
 | 
			
		||||
 */
 | 
			
		||||
public interface EmailGroup {
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
package org.dromara.common.core.validate.auth;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author Michelle.Chung
 | 
			
		||||
 */
 | 
			
		||||
public interface PasswordGroup {
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
package org.dromara.common.core.validate.auth;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author Michelle.Chung
 | 
			
		||||
 */
 | 
			
		||||
public interface SmsGroup {
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
package org.dromara.common.core.validate.auth;
 | 
			
		||||
 | 
			
		||||
public interface SocialGroup {
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
package org.dromara.common.core.validate.auth;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author Michelle.Chung
 | 
			
		||||
 */
 | 
			
		||||
public interface WechatGroup {
 | 
			
		||||
}
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -37,6 +32,23 @@
 | 
			
		||||
            <artifactId>hutool-crypto</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework</groupId>
 | 
			
		||||
            <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>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
package org.dromara.common.encrypt.annotation;
 | 
			
		||||
 | 
			
		||||
import java.lang.annotation.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 强制加密注解
 | 
			
		||||
 *
 | 
			
		||||
 * @author Michelle.Chung
 | 
			
		||||
 */
 | 
			
		||||
@Documented
 | 
			
		||||
@Target({ElementType.METHOD})
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
public @interface ApiEncrypt {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 响应加密忽略,默认不加密,为 true 时加密
 | 
			
		||||
     */
 | 
			
		||||
    boolean response() default false;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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,13 +1,23 @@
 | 
			
		||||
package org.dromara.common.encrypt.core;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import cn.hutool.core.util.ReflectUtil;
 | 
			
		||||
import org.dromara.common.encrypt.annotation.EncryptField;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.apache.ibatis.io.Resources;
 | 
			
		||||
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;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
@@ -18,6 +28,7 @@ import java.util.stream.Collectors;
 | 
			
		||||
 * @version 4.6.0
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
public class EncryptorManager {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -30,20 +41,24 @@ public class EncryptorManager {
 | 
			
		||||
     */
 | 
			
		||||
    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 -> {
 | 
			
		||||
            Field[] declaredFields = clazz.getDeclaredFields();
 | 
			
		||||
            Set<Field> fieldSet = Arrays.stream(declaredFields).filter(field ->
 | 
			
		||||
                    field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
 | 
			
		||||
                .collect(Collectors.toSet());
 | 
			
		||||
            for (Field field : fieldSet) {
 | 
			
		||||
                field.setAccessible(true);
 | 
			
		||||
            }
 | 
			
		||||
            return fieldSet;
 | 
			
		||||
        });
 | 
			
		||||
        if (ObjectUtil.isNotNull(fieldCache)) {
 | 
			
		||||
            return fieldCache.get(sourceClazz);
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -91,4 +106,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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,18 @@ package org.dromara.common.encrypt.filter;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import jakarta.servlet.*;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import org.dromara.common.core.constant.HttpStatus;
 | 
			
		||||
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.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;
 | 
			
		||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
@@ -25,25 +33,78 @@ public class CryptoFilter implements Filter {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 | 
			
		||||
        ServletRequest requestWrapper = null;
 | 
			
		||||
        HttpServletRequest servletRequest = (HttpServletRequest) request;
 | 
			
		||||
        // 是否为 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.getPublicKey(), properties.getPrivateKey(), properties.getHeaderFlag());
 | 
			
		||||
        HttpServletResponse servletResponse = (HttpServletResponse) response;
 | 
			
		||||
        // 获取加密注解
 | 
			
		||||
        ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest);
 | 
			
		||||
        boolean responseFlag = apiEncrypt != null && apiEncrypt.response();
 | 
			
		||||
        ServletRequest requestWrapper = null;
 | 
			
		||||
        ServletResponse responseWrapper = null;
 | 
			
		||||
        EncryptResponseBodyWrapper responseBodyWrapper = null;
 | 
			
		||||
 | 
			
		||||
        // 是否为 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;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        chain.doFilter(ObjectUtil.defaultIfNull(requestWrapper, request), response);
 | 
			
		||||
        // 判断是否响应加密
 | 
			
		||||
        if (responseFlag) {
 | 
			
		||||
            responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
 | 
			
		||||
            responseWrapper = responseBodyWrapper;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        chain.doFilter(
 | 
			
		||||
            ObjectUtil.defaultIfNull(requestWrapper, request),
 | 
			
		||||
            ObjectUtil.defaultIfNull(responseWrapper, response));
 | 
			
		||||
 | 
			
		||||
        if (responseFlag) {
 | 
			
		||||
            servletResponse.reset();
 | 
			
		||||
            // 对原始内容加密
 | 
			
		||||
            String encryptContent = responseBodyWrapper.getEncryptContent(
 | 
			
		||||
                servletResponse, properties.getPublicKey(), properties.getHeaderFlag());
 | 
			
		||||
            // 对加密后的内容写出
 | 
			
		||||
            servletResponse.getWriter().write(encryptContent);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 ApiEncrypt 注解
 | 
			
		||||
     */
 | 
			
		||||
    private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) {
 | 
			
		||||
        RequestMappingHandlerMapping handlerMapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
 | 
			
		||||
        // 获取注解
 | 
			
		||||
        try {
 | 
			
		||||
            HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest);
 | 
			
		||||
            if (ObjectUtil.isNotNull(mappingHandler)) {
 | 
			
		||||
                Object handler = mappingHandler.getHandler();
 | 
			
		||||
                if (ObjectUtil.isNotNull(handler)) {
 | 
			
		||||
                    // 从handler获取注解
 | 
			
		||||
                    if (handler instanceof HandlerMethod handlerMethod) {
 | 
			
		||||
                        return handlerMethod.getMethodAnnotation(ApiEncrypt.class);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void destroy() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {
 | 
			
		||||
 | 
			
		||||
    private final byte[] body;
 | 
			
		||||
 | 
			
		||||
    public DecryptRequestBodyWrapper(HttpServletRequest request, String publicKey, String privateKey, String headerFlag) throws IOException {
 | 
			
		||||
    public DecryptRequestBodyWrapper(HttpServletRequest request, String privateKey, String headerFlag) throws IOException {
 | 
			
		||||
        super(request);
 | 
			
		||||
        // 获取 AES 密码 采用 RSA 加密
 | 
			
		||||
        String headerRsa = request.getHeader(headerFlag);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,121 @@
 | 
			
		||||
package org.dromara.common.encrypt.filter;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.util.RandomUtil;
 | 
			
		||||
import jakarta.servlet.ServletOutputStream;
 | 
			
		||||
import jakarta.servlet.WriteListener;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponseWrapper;
 | 
			
		||||
import org.dromara.common.encrypt.utils.EncryptUtils;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 加密响应参数包装类
 | 
			
		||||
 *
 | 
			
		||||
 * @author Michelle.Chung
 | 
			
		||||
 */
 | 
			
		||||
public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper {
 | 
			
		||||
 | 
			
		||||
    private final ByteArrayOutputStream byteArrayOutputStream;
 | 
			
		||||
    private final ServletOutputStream servletOutputStream;
 | 
			
		||||
    private final PrintWriter printWriter;
 | 
			
		||||
 | 
			
		||||
    public EncryptResponseBodyWrapper(HttpServletResponse response) throws IOException {
 | 
			
		||||
        super(response);
 | 
			
		||||
        this.byteArrayOutputStream = new ByteArrayOutputStream();
 | 
			
		||||
        this.servletOutputStream = this.getOutputStream();
 | 
			
		||||
        this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public PrintWriter getWriter() {
 | 
			
		||||
        return printWriter;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void flushBuffer() throws IOException {
 | 
			
		||||
        if (servletOutputStream != null) {
 | 
			
		||||
            servletOutputStream.flush();
 | 
			
		||||
        }
 | 
			
		||||
        if (printWriter != null) {
 | 
			
		||||
            printWriter.flush();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void reset() {
 | 
			
		||||
        byteArrayOutputStream.reset();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public byte[] getResponseData() throws IOException {
 | 
			
		||||
        flushBuffer();
 | 
			
		||||
        return byteArrayOutputStream.toByteArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getContent() throws IOException {
 | 
			
		||||
        flushBuffer();
 | 
			
		||||
        return byteArrayOutputStream.toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取加密内容
 | 
			
		||||
     *
 | 
			
		||||
     * @param servletResponse response
 | 
			
		||||
     * @param publicKey       RSA公钥 (用于加密 AES 秘钥)
 | 
			
		||||
     * @param headerFlag      请求头标志
 | 
			
		||||
     * @return 加密内容
 | 
			
		||||
     * @throws IOException
 | 
			
		||||
     */
 | 
			
		||||
    public String getEncryptContent(HttpServletResponse servletResponse, String publicKey, String headerFlag) throws IOException {
 | 
			
		||||
        // 生成秘钥
 | 
			
		||||
        String aesPassword = RandomUtil.randomString(32);
 | 
			
		||||
        // 秘钥使用 Base64 编码
 | 
			
		||||
        String encryptAes = EncryptUtils.encryptByBase64(aesPassword);
 | 
			
		||||
        // Rsa 公钥加密 Base64 编码
 | 
			
		||||
        String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey);
 | 
			
		||||
 | 
			
		||||
        // 设置响应头
 | 
			
		||||
        servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag);
 | 
			
		||||
        servletResponse.setHeader(headerFlag, encryptPassword);
 | 
			
		||||
        servletResponse.setHeader("Access-Control-Allow-Origin", "*");
 | 
			
		||||
        servletResponse.setHeader("Access-Control-Allow-Methods", "*");
 | 
			
		||||
        servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
 | 
			
		||||
 | 
			
		||||
        // 获取原始内容
 | 
			
		||||
        String originalBody = this.getContent();
 | 
			
		||||
        // 对内容进行加密
 | 
			
		||||
        return EncryptUtils.encryptByAes(originalBody, aesPassword);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ServletOutputStream getOutputStream() throws IOException {
 | 
			
		||||
        return new ServletOutputStream() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public boolean isReady() {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void setWriteListener(WriteListener writeListener) {
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void write(int b) throws IOException {
 | 
			
		||||
                byteArrayOutputStream.write(b);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void write(byte[] b) throws IOException {
 | 
			
		||||
                byteArrayOutputStream.write(b);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void write(byte[] b, int off, int len) throws IOException {
 | 
			
		||||
                byteArrayOutputStream.write(b, off, len);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -73,7 +73,11 @@ public class MybatisDecryptInterceptor implements Interceptor {
 | 
			
		||||
            list.forEach(this::decryptHandler);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 不在缓存中的类,就是没有加密注解的类(当然也有可能是typeAliasesPackage写错)
 | 
			
		||||
        Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
 | 
			
		||||
        if(ObjectUtil.isNull(fields)){
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            for (Field field : fields) {
 | 
			
		||||
                field.set(sourceObject, this.decryptField(Convert.toStr(field.get(sourceObject)), field));
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,11 @@ public class MybatisEncryptInterceptor implements Interceptor {
 | 
			
		||||
            list.forEach(this::encryptHandler);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 不在缓存中的类,就是没有加密注解的类(当然也有可能是typeAliasesPackage写错)
 | 
			
		||||
        Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
 | 
			
		||||
        if(ObjectUtil.isNull(fields)){
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            for (Field field : fields) {
 | 
			
		||||
                field.set(sourceObject, this.encryptField(Convert.toStr(field.get(sourceObject)), field));
 | 
			
		||||
 
 | 
			
		||||
@@ -21,14 +21,14 @@ public class ApiDecryptProperties {
 | 
			
		||||
     */
 | 
			
		||||
    private String headerFlag;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 公钥
 | 
			
		||||
     * 响应加密公钥
 | 
			
		||||
     */
 | 
			
		||||
    private String publicKey;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 私钥
 | 
			
		||||
     * 请求解密私钥
 | 
			
		||||
     */
 | 
			
		||||
    private String privateKey;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,11 @@
 | 
			
		||||
            <groupId>com.alibaba</groupId>
 | 
			
		||||
            <artifactId>easyexcel</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <artifactId>commons-compress</artifactId>
 | 
			
		||||
            <groupId>org.apache.commons</groupId>
 | 
			
		||||
            <version>1.26.2</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,4 +21,9 @@ public @interface CellMerge {
 | 
			
		||||
	 */
 | 
			
		||||
	int index() default -1;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 合并需要依赖的其他字段名称
 | 
			
		||||
     */
 | 
			
		||||
    String[] mergeBy() default {};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,12 @@
 | 
			
		||||
package org.dromara.common.excel.core;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.util.ReflectUtil;
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelProperty;
 | 
			
		||||
import com.alibaba.excel.metadata.Head;
 | 
			
		||||
import com.alibaba.excel.write.handler.WorkbookWriteHandler;
 | 
			
		||||
import com.alibaba.excel.write.handler.context.WorkbookWriteHandlerContext;
 | 
			
		||||
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
@@ -15,10 +19,7 @@ import org.dromara.common.core.utils.reflect.ReflectUtils;
 | 
			
		||||
import org.dromara.common.excel.annotation.CellMerge;
 | 
			
		||||
 | 
			
		||||
import java.lang.reflect.Field;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 列值重复合并策略
 | 
			
		||||
@@ -26,7 +27,7 @@ import java.util.Map;
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class CellMergeStrategy extends AbstractMergeStrategy {
 | 
			
		||||
public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler {
 | 
			
		||||
 | 
			
		||||
    private final List<CellRangeAddress> cellList;
 | 
			
		||||
    private final boolean hasTitle;
 | 
			
		||||
@@ -41,17 +42,28 @@ public class CellMergeStrategy extends AbstractMergeStrategy {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
 | 
			
		||||
        // judge the list is not null
 | 
			
		||||
        if (CollUtil.isNotEmpty(cellList)) {
 | 
			
		||||
            // the judge is necessary
 | 
			
		||||
            if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == 0) {
 | 
			
		||||
                for (CellRangeAddress item : cellList) {
 | 
			
		||||
                    sheet.addMergedRegion(item);
 | 
			
		||||
        //单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
 | 
			
		||||
        final int rowIndex = cell.getRowIndex();
 | 
			
		||||
        if (CollUtil.isNotEmpty(cellList)){
 | 
			
		||||
            for (CellRangeAddress cellAddresses : cellList) {
 | 
			
		||||
                final int firstRow = cellAddresses.getFirstRow();
 | 
			
		||||
                if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
 | 
			
		||||
                    cell.setBlank();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) {
 | 
			
		||||
        //当前表格写完后,统一写入
 | 
			
		||||
        if (CollUtil.isNotEmpty(cellList)){
 | 
			
		||||
            for (CellRangeAddress item : cellList) {
 | 
			
		||||
                context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SneakyThrows
 | 
			
		||||
    private List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
 | 
			
		||||
        List<CellRangeAddress> cellList = new ArrayList<>();
 | 
			
		||||
@@ -93,15 +105,21 @@ public class CellMergeStrategy extends AbstractMergeStrategy {
 | 
			
		||||
                        // 空值跳过不合并
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (!cellValue.equals(val)) {
 | 
			
		||||
                        if (i - repeatCell.getCurrent() > 1) {
 | 
			
		||||
                        if ((i - repeatCell.getCurrent() > 1)) {
 | 
			
		||||
                            cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
 | 
			
		||||
                        }
 | 
			
		||||
                        map.put(field, new RepeatCell(val, i));
 | 
			
		||||
                    } else if (i == list.size() - 1) {
 | 
			
		||||
                        if (i > repeatCell.getCurrent()) {
 | 
			
		||||
                        if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
 | 
			
		||||
                            cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if (!isMerge(list, i, field)) {
 | 
			
		||||
                        if ((i - repeatCell.getCurrent() > 1)) {
 | 
			
		||||
                            cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
 | 
			
		||||
                        }
 | 
			
		||||
                        map.put(field, new RepeatCell(val, i));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -109,6 +127,24 @@ public class CellMergeStrategy extends AbstractMergeStrategy {
 | 
			
		||||
        return cellList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isMerge(List<?> list, int i, Field field) {
 | 
			
		||||
        boolean isMerge = true;
 | 
			
		||||
        CellMerge cm = field.getAnnotation(CellMerge.class);
 | 
			
		||||
        final String[] mergeBy = cm.mergeBy();
 | 
			
		||||
        if (StrUtil.isAllNotBlank(mergeBy)) {
 | 
			
		||||
            //比对当前list(i)和list(i - 1)的各个属性值一一比对 如果全为真 则为真
 | 
			
		||||
            for (String fieldName : mergeBy) {
 | 
			
		||||
                final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName);
 | 
			
		||||
                final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName);
 | 
			
		||||
                if (!Objects.equals(valPre, valCurrent)) {
 | 
			
		||||
                    //依赖字段如有任一不等值,则标记为不可合并
 | 
			
		||||
                    isMerge = false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return isMerge;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Data
 | 
			
		||||
    @AllArgsConstructor
 | 
			
		||||
    static class RepeatCell {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import org.dromara.common.core.exception.ServiceException;
 | 
			
		||||
import org.dromara.common.core.service.DictService;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StreamUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
 | 
			
		||||
import org.dromara.common.excel.annotation.ExcelEnumFormat;
 | 
			
		||||
 | 
			
		||||
@@ -99,15 +100,16 @@ public class ExcelDownHandler implements SheetWriteHandler {
 | 
			
		||||
                ExcelDictFormat format = field.getDeclaredAnnotation(ExcelDictFormat.class);
 | 
			
		||||
                String dictType = format.dictType();
 | 
			
		||||
                String converterExp = format.readConverterExp();
 | 
			
		||||
                if (StrUtil.isNotBlank(dictType)) {
 | 
			
		||||
                if (StringUtils.isNotBlank(dictType)) {
 | 
			
		||||
                    // 如果传递了字典名,则依据字典建立下拉
 | 
			
		||||
                    Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
 | 
			
		||||
                        .orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
 | 
			
		||||
                        .values();
 | 
			
		||||
                    options = new ArrayList<>(values);
 | 
			
		||||
                } else if (StrUtil.isNotBlank(converterExp)) {
 | 
			
		||||
                } else if (StringUtils.isNotBlank(converterExp)) {
 | 
			
		||||
                    // 如果指定了确切的值,则直接解析确切的值
 | 
			
		||||
                    options = StrUtil.split(converterExp, format.separator(), true, true);
 | 
			
		||||
                    List<String> strList = StringUtils.splitList(converterExp, format.separator());
 | 
			
		||||
                    options = StreamUtils.toList(strList, s -> StringUtils.split(s, "=")[1]);
 | 
			
		||||
                }
 | 
			
		||||
            } else if (field.isAnnotationPresent(ExcelEnumFormat.class)) {
 | 
			
		||||
                // 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
 | 
			
		||||
 
 | 
			
		||||
@@ -269,6 +269,26 @@ public class ExcelUtil {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 多sheet模板导出 模板格式为 {key.属性}
 | 
			
		||||
     *
 | 
			
		||||
     * @param filename     文件名
 | 
			
		||||
     * @param templatePath 模板路径 resource 目录下的路径包括模板文件名
 | 
			
		||||
     *                     例如: excel/temp.xlsx
 | 
			
		||||
     *                     重点: 模板文件必须放置到启动类对应的 resource 目录下
 | 
			
		||||
     * @param data         模板需要的数据
 | 
			
		||||
     * @param response     响应体
 | 
			
		||||
     */
 | 
			
		||||
    public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
 | 
			
		||||
        try {
 | 
			
		||||
            resetResponse(filename, response);
 | 
			
		||||
            ServletOutputStream os = response.getOutputStream();
 | 
			
		||||
            exportTemplateMultiSheet(data, templatePath, os);
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            throw new RuntimeException("导出Excel异常");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 多表多数据模板导出 模板格式为 {key.属性}
 | 
			
		||||
     *
 | 
			
		||||
@@ -303,6 +323,42 @@ public class ExcelUtil {
 | 
			
		||||
        excelWriter.finish();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 多sheet模板导出 模板格式为 {key.属性}
 | 
			
		||||
     *
 | 
			
		||||
     * @param templatePath 模板路径 resource 目录下的路径包括模板文件名
 | 
			
		||||
     *                     例如: excel/temp.xlsx
 | 
			
		||||
     *                     重点: 模板文件必须放置到启动类对应的 resource 目录下
 | 
			
		||||
     * @param data         模板需要的数据
 | 
			
		||||
     * @param os           输出流
 | 
			
		||||
     */
 | 
			
		||||
    public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
 | 
			
		||||
        ClassPathResource templateResource = new ClassPathResource(templatePath);
 | 
			
		||||
        ExcelWriter excelWriter = EasyExcel.write(os)
 | 
			
		||||
            .withTemplate(templateResource.getStream())
 | 
			
		||||
            .autoCloseStream(false)
 | 
			
		||||
            // 大数值自动转换 防止失真
 | 
			
		||||
            .registerConverter(new ExcelBigNumberConvert())
 | 
			
		||||
            .build();
 | 
			
		||||
        if (CollUtil.isEmpty(data)) {
 | 
			
		||||
            throw new IllegalArgumentException("数据为空");
 | 
			
		||||
        }
 | 
			
		||||
        for (int i = 0; i < data.size(); i++) {
 | 
			
		||||
            WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
 | 
			
		||||
            for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
 | 
			
		||||
                // 设置列表后续还有数据
 | 
			
		||||
                FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
 | 
			
		||||
                if (map.getValue() instanceof Collection) {
 | 
			
		||||
                    // 多表导出必须使用 FillWrapper
 | 
			
		||||
                    excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
 | 
			
		||||
                } else {
 | 
			
		||||
                    excelWriter.fill(map.getValue(), writeSheet);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        excelWriter.finish();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 重置响应体
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,13 @@ import cn.dev33.satoken.SaManager;
 | 
			
		||||
import cn.hutool.core.util.ArrayUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import cn.hutool.crypto.SecureUtil;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import org.aspectj.lang.JoinPoint;
 | 
			
		||||
import org.aspectj.lang.annotation.AfterReturning;
 | 
			
		||||
import org.aspectj.lang.annotation.AfterThrowing;
 | 
			
		||||
import org.aspectj.lang.annotation.Aspect;
 | 
			
		||||
import org.aspectj.lang.annotation.Before;
 | 
			
		||||
import org.dromara.common.core.constant.GlobalConstants;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.core.exception.ServiceException;
 | 
			
		||||
@@ -13,13 +20,6 @@ import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
 | 
			
		||||
import org.dromara.common.json.utils.JsonUtils;
 | 
			
		||||
import org.dromara.common.redis.utils.RedisUtils;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import org.aspectj.lang.JoinPoint;
 | 
			
		||||
import org.aspectj.lang.annotation.AfterReturning;
 | 
			
		||||
import org.aspectj.lang.annotation.AfterThrowing;
 | 
			
		||||
import org.aspectj.lang.annotation.Aspect;
 | 
			
		||||
import org.aspectj.lang.annotation.Before;
 | 
			
		||||
import org.springframework.validation.BindingResult;
 | 
			
		||||
import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
@@ -127,7 +127,7 @@ public class RepeatSubmitAspect {
 | 
			
		||||
    public boolean isFilterObject(final Object o) {
 | 
			
		||||
        Class<?> clazz = o.getClass();
 | 
			
		||||
        if (clazz.isArray()) {
 | 
			
		||||
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
 | 
			
		||||
            return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
 | 
			
		||||
        } else if (Collection.class.isAssignableFrom(clazz)) {
 | 
			
		||||
            Collection collection = (Collection) o;
 | 
			
		||||
            for (Object value : collection) {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,14 +22,14 @@
 | 
			
		||||
            <artifactId>spring-boot-autoconfigure</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <!--PowerJob-->
 | 
			
		||||
        <!-- SnailJob client -->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>tech.powerjob</groupId>
 | 
			
		||||
            <artifactId>powerjob-worker-spring-boot-starter</artifactId>
 | 
			
		||||
            <groupId>com.aizuda</groupId>
 | 
			
		||||
            <artifactId>snail-job-client-starter</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>tech.powerjob</groupId>
 | 
			
		||||
            <artifactId>powerjob-official-processors</artifactId>
 | 
			
		||||
            <groupId>com.aizuda</groupId>
 | 
			
		||||
            <artifactId>snail-job-client-job-core</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
package org.dromara.common.job.config;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 | 
			
		||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import org.springframework.scheduling.annotation.EnableScheduling;
 | 
			
		||||
import tech.powerjob.worker.PowerJobWorker;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 启动定时任务
 | 
			
		||||
 * @author yhan219
 | 
			
		||||
 * @since 2023/6/2
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
@ConditionalOnBean(PowerJobWorker.class)
 | 
			
		||||
@ConditionalOnProperty(prefix = "powerjob.worker", name = "enabled", havingValue = "true")
 | 
			
		||||
@EnableScheduling
 | 
			
		||||
public class PowerJobConfig {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
package org.dromara.common.job.config;
 | 
			
		||||
 | 
			
		||||
import ch.qos.logback.classic.Logger;
 | 
			
		||||
import ch.qos.logback.classic.LoggerContext;
 | 
			
		||||
import ch.qos.logback.classic.spi.ILoggingEvent;
 | 
			
		||||
import com.aizuda.snailjob.client.common.appender.SnailLogbackAppender;
 | 
			
		||||
import com.aizuda.snailjob.client.common.event.SnailClientStartingEvent;
 | 
			
		||||
import com.aizuda.snailjob.client.starter.EnableSnailJob;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
			
		||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 | 
			
		||||
import org.springframework.context.event.EventListener;
 | 
			
		||||
import org.springframework.scheduling.annotation.EnableScheduling;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 启动定时任务
 | 
			
		||||
 *
 | 
			
		||||
 * @author opensnail
 | 
			
		||||
 * @date 2024-05-17
 | 
			
		||||
 */
 | 
			
		||||
@AutoConfiguration
 | 
			
		||||
@ConditionalOnProperty(prefix = "snail-job", name = "enabled", havingValue = "true")
 | 
			
		||||
@EnableScheduling
 | 
			
		||||
@EnableSnailJob
 | 
			
		||||
public class SnailJobConfig {
 | 
			
		||||
 | 
			
		||||
    @EventListener(SnailClientStartingEvent.class)
 | 
			
		||||
    public void onStarting(SnailClientStartingEvent event) {
 | 
			
		||||
        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
 | 
			
		||||
        SnailLogbackAppender<ILoggingEvent> ca = new SnailLogbackAppender<>();
 | 
			
		||||
        ca.setName("snail_log_appender");
 | 
			
		||||
        ca.start();
 | 
			
		||||
        Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
 | 
			
		||||
        rootLogger.addAppender(ca);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
org.dromara.common.job.config.SnailJobConfig
 | 
			
		||||
@@ -7,10 +7,10 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 | 
			
		||||
import com.fasterxml.jackson.core.type.TypeReference;
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import lombok.AccessLevel;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
@@ -30,6 +30,13 @@ public class JsonUtils {
 | 
			
		||||
        return OBJECT_MAPPER;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 将对象转换为JSON格式的字符串
 | 
			
		||||
     *
 | 
			
		||||
     * @param object 要转换的对象
 | 
			
		||||
     * @return JSON格式的字符串,如果对象为null,则返回null
 | 
			
		||||
     * @throws RuntimeException 如果转换过程中发生JSON处理异常,则抛出运行时异常
 | 
			
		||||
     */
 | 
			
		||||
    public static String toJsonString(Object object) {
 | 
			
		||||
        if (ObjectUtil.isNull(object)) {
 | 
			
		||||
            return null;
 | 
			
		||||
@@ -41,6 +48,15 @@ public class JsonUtils {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 将JSON格式的字符串转换为指定类型的对象
 | 
			
		||||
     *
 | 
			
		||||
     * @param text  JSON格式的字符串
 | 
			
		||||
     * @param clazz 要转换的目标对象类型
 | 
			
		||||
     * @param <T>   目标对象的泛型类型
 | 
			
		||||
     * @return 转换后的对象,如果字符串为空则返回null
 | 
			
		||||
     * @throws RuntimeException 如果转换过程中发生IO异常,则抛出运行时异常
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> T parseObject(String text, Class<T> clazz) {
 | 
			
		||||
        if (StringUtils.isEmpty(text)) {
 | 
			
		||||
            return null;
 | 
			
		||||
@@ -52,6 +68,15 @@ public class JsonUtils {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 将字节数组转换为指定类型的对象
 | 
			
		||||
     *
 | 
			
		||||
     * @param bytes 字节数组
 | 
			
		||||
     * @param clazz 要转换的目标对象类型
 | 
			
		||||
     * @param <T>   目标对象的泛型类型
 | 
			
		||||
     * @return 转换后的对象,如果字节数组为空则返回null
 | 
			
		||||
     * @throws RuntimeException 如果转换过程中发生IO异常,则抛出运行时异常
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
 | 
			
		||||
        if (ArrayUtil.isEmpty(bytes)) {
 | 
			
		||||
            return null;
 | 
			
		||||
@@ -63,6 +88,15 @@ public class JsonUtils {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 将JSON格式的字符串转换为指定类型的对象,支持复杂类型
 | 
			
		||||
     *
 | 
			
		||||
     * @param text          JSON格式的字符串
 | 
			
		||||
     * @param typeReference 指定类型的TypeReference对象
 | 
			
		||||
     * @param <T>           目标对象的泛型类型
 | 
			
		||||
     * @return 转换后的对象,如果字符串为空则返回null
 | 
			
		||||
     * @throws RuntimeException 如果转换过程中发生IO异常,则抛出运行时异常
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> T parseObject(String text, TypeReference<T> typeReference) {
 | 
			
		||||
        if (StringUtils.isBlank(text)) {
 | 
			
		||||
            return null;
 | 
			
		||||
@@ -74,6 +108,13 @@ public class JsonUtils {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 将JSON格式的字符串转换为Dict对象
 | 
			
		||||
     *
 | 
			
		||||
     * @param text JSON格式的字符串
 | 
			
		||||
     * @return 转换后的Dict对象,如果字符串为空或者不是JSON格式则返回null
 | 
			
		||||
     * @throws RuntimeException 如果转换过程中发生IO异常,则抛出运行时异常
 | 
			
		||||
     */
 | 
			
		||||
    public static Dict parseMap(String text) {
 | 
			
		||||
        if (StringUtils.isBlank(text)) {
 | 
			
		||||
            return null;
 | 
			
		||||
@@ -88,6 +129,13 @@ public class JsonUtils {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 将JSON格式的字符串转换为Dict对象的列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param text JSON格式的字符串
 | 
			
		||||
     * @return 转换后的Dict对象的列表,如果字符串为空则返回null
 | 
			
		||||
     * @throws RuntimeException 如果转换过程中发生IO异常,则抛出运行时异常
 | 
			
		||||
     */
 | 
			
		||||
    public static List<Dict> parseArrayMap(String text) {
 | 
			
		||||
        if (StringUtils.isBlank(text)) {
 | 
			
		||||
            return null;
 | 
			
		||||
@@ -99,6 +147,15 @@ public class JsonUtils {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 将JSON格式的字符串转换为指定类型对象的列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param text  JSON格式的字符串
 | 
			
		||||
     * @param clazz 要转换的目标对象类型
 | 
			
		||||
     * @param <T>   目标对象的泛型类型
 | 
			
		||||
     * @return 转换后的对象的列表,如果字符串为空则返回空列表
 | 
			
		||||
     * @throws RuntimeException 如果转换过程中发生IO异常,则抛出运行时异常
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> List<T> parseArray(String text, Class<T> clazz) {
 | 
			
		||||
        if (StringUtils.isEmpty(text)) {
 | 
			
		||||
            return new ArrayList<>();
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user