mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-29 14:43:59 +08:00 
			
		
		
		
	Compare commits
	
		
			485 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 65c695d9ce | ||
|   | 57253fe46a | ||
|   | 4e5c443440 | ||
|   | 0b3b73d8ec | ||
|   | 921eabc134 | ||
|   | 0faa428751 | ||
|   | f71a2fdd63 | ||
|   | 4eb9ed8aba | ||
|   | d7b549abb8 | ||
|   | 95d723c578 | ||
|   | 2fcd853e86 | ||
|   | 07eef7c812 | ||
|   | b01e0757fa | ||
|   | 32844a20c6 | ||
|   | 5b6532c601 | ||
|   | 2c5b4b4027 | ||
|   | 72d7ecf195 | ||
|   | 2cfa6b4306 | ||
|   | 6f6ffde0ab | ||
|   | 1694739a16 | ||
|   | 95d1e8bfca | ||
|   | 60dec08e3c | ||
|   | a99d71be93 | ||
|   | f1331b6a0c | ||
|   | 10d66b642b | ||
|   | cd2310e4a8 | ||
|   | 1b399cf6b0 | ||
|   | 877445bc0a | ||
|   | 9a5b345bde | ||
|   | fc9e8ea7b3 | ||
|   | 32be6fcfc1 | ||
|   | 49847236c2 | ||
|   | d8424443e6 | ||
|   | f3b571ec3f | ||
|   | 99318bb5d7 | ||
|   | 1aa154c9aa | ||
|   | c65d8a445b | ||
|   | 80f4f85570 | ||
|   | 5beee43a6b | ||
|   | 8d6ae203a0 | ||
|   | 4353479a5c | ||
|   | 34d7687f9e | ||
|   | b1dc3cf4af | ||
|   | 6a58b95933 | ||
|   | d3badfd02b | ||
|   | 0098be057b | ||
|   | 6f972aa515 | ||
|   | 7407ba6313 | ||
|   | 1c79de207b | ||
|   | 257c79db92 | ||
|   | 9d1934a308 | ||
|   | d70f959902 | ||
|   | e4d810222f | ||
|   | bc1af4ae07 | ||
|   | 6e688ef43f | ||
|   | f0fe1b23dc | ||
|   | aaf2006401 | ||
|   | b821e26935 | ||
|   | 7ae4287157 | ||
|   | c6fcc38a65 | ||
|   | ab2d5c8853 | ||
|   | 5e557ff0bc | ||
|   | 918ca449a1 | ||
|   | 8e73368008 | ||
|   | f3c1faf672 | ||
|   | d6df04dd6a | ||
|   | b1b9e51ab6 | ||
|   | e49d4770ac | ||
|   | 8fa1075511 | ||
|   | 9a70169b94 | ||
|   | fefb928237 | ||
|   | ad7e700d0d | ||
|   | 1699c69147 | ||
|   | 1695f7cece | ||
|   | 052c27f907 | ||
|   | dc46c32b30 | ||
|   | fa63349bb2 | ||
|   | ffe26448a6 | ||
|   | 4af51e8a84 | ||
|   | 1e453cf5a5 | ||
|   | 591282b87d | ||
|   | e87528d520 | ||
|   | d79eb0411d | ||
|   | ac1e0a4cf7 | ||
|   | 9525eab130 | ||
|   | 89b317496c | ||
|   | 13be91e78b | ||
|   | f68c1437f3 | ||
|   | 4c64c969bb | ||
|   | b4bf3b5138 | ||
|   | 083bc4b400 | ||
|   | e8683c5bcc | ||
|   | 80e0d1de91 | ||
|   | dbe841037e | ||
|   | bdd537c33c | ||
|   | c0c3846094 | ||
|   | 9e8710e7d2 | ||
|   | 475553fdf6 | ||
|   | 9d570f5b45 | ||
|   | af7fafd34f | ||
|   | d43130f4fc | ||
|   | 7500194620 | ||
|   | eb27c29144 | ||
|   | 43260b3e24 | ||
|   | f80713f0aa | ||
|   | 0c4bdc7ad1 | ||
|   | 811cff7bd0 | ||
|   | 30269aa75c | ||
|   | e345ef7083 | ||
|   | f559c9b8f7 | ||
|   | f4af0916b2 | ||
|   | f15f14f28d | ||
|   | 834f44f58d | ||
|   | b36f45dcf4 | ||
|   | 11ba21c9a8 | ||
|   | b045557ce1 | ||
|   | 0dd251a3f6 | ||
|   | 793acb1725 | ||
|   | 921243e8bd | ||
|   | bd9d7a90d9 | ||
|   | cc444a4cea | ||
|   | 38ca1fa168 | ||
|   | 7a552b87ec | ||
|   | 36923d3190 | ||
|   | a9d3017123 | ||
|   | 313acd4976 | ||
|   | a4c91bb268 | ||
|   | f9b566984b | ||
|   | 8dd261854d | ||
|   | 7351e62d87 | ||
|   | 0593ae720b | ||
|   | a0a7b08e08 | ||
|   | 9a3bc6b8b3 | ||
|   | 5acae17f71 | ||
|   | f1e5b76ef2 | ||
|   | 53c628fde9 | ||
|   | baca0a70c0 | ||
|   | 3e8d0af404 | ||
|   | cf9a91d9d5 | ||
|   | 02b9e282c6 | ||
|   | 9ce87f235f | ||
|   | e329bea1b2 | ||
|   | 8086e7b54d | ||
|   | f7a875606e | ||
|   | 196eaf85f4 | ||
|   | 876a55668e | ||
|   | 05bd21bdd5 | ||
|   | fb51a08cc6 | ||
|   | dd83d7f4d3 | ||
|   | 842a56f7ce | ||
|   | 9246a6e797 | ||
|   | 8ad693f717 | ||
|   | f4c2ee7cc4 | ||
|   | 6043441faa | ||
|   | 4a065c3710 | ||
|   | 0ef800bdd7 | ||
|   | 56eaa1910d | ||
|   | 201788e286 | ||
|   | 506e0f144f | ||
|   | 72f68bfdd9 | ||
|   | 2f9869b11d | ||
|   | 8ffcf6498c | ||
|   | d224ae1923 | ||
|   | fed2063a19 | ||
|   | db2810cdd7 | ||
|   | 4f1a6781ef | ||
|   | beffa5d5a4 | ||
|   | 7a20f1de07 | ||
|   | cd25cf726b | ||
|   | d6b1bc3842 | ||
|   | a4385fb9bb | ||
|   | 7045f2b8ea | ||
|   | 07ca1a4de8 | ||
|   | 24f289e692 | ||
|   | 01bcdaae2d | ||
|   | 55890008d1 | ||
|   | 5ab9b01879 | ||
|   | e4abb333b3 | ||
|   | 09f476c745 | ||
|   | 8806e68dce | ||
|   | 2ef1e25cd8 | ||
|   | 10e7f202aa | ||
|   | ccd7000c09 | ||
|   | 8ee7b798cf | ||
|   | 7733cf5bf0 | ||
|   | a05ce86dd7 | ||
|   | 91f51c32e8 | ||
|   | f910202bba | ||
|   | 6d77194a8f | ||
|   | 9deb89c15f | ||
|   | 4b62a092b4 | ||
|   | 81c8f626f9 | ||
|   | 3e846c42fb | ||
|   | 63ad7fd766 | ||
|   | 9ff1e9aa34 | ||
|   | 8d162b6f3d | ||
|   | 9844d10bef | ||
|   | b908fa8489 | ||
|   | 15a10643a7 | ||
|   | 299617aca1 | ||
|   | 45647d697a | ||
|   | 48f5105d38 | ||
|   | fe1c741d68 | ||
|   | fa42cc1f00 | ||
|   | 42cf5e7a81 | ||
|   | 47905e1aa1 | ||
|   | 9a8e907df3 | ||
|   | 106fe85582 | ||
|   | 4b3571bd57 | ||
|   | 96b537401a | ||
|   | 721c9eb057 | ||
|   | 51701bf6d6 | ||
|   | dbde68bd56 | ||
|   | ad2c9f585a | ||
|   | 562093c468 | ||
|   | b0295584a3 | ||
|   | 208c54de98 | ||
|   | 63e2d941a1 | ||
|   | 3956838e9c | ||
|   | abeee58bb0 | ||
|   | d5b1b49722 | ||
|   | 564ed03ff8 | ||
|   | 70db4c76b4 | ||
|   | d059f7975b | ||
|   | 4e74e6dc2d | ||
|   | b6deb96658 | ||
|   | 3839e966be | ||
|   | 3dd035849c | ||
|   | 3d6532b5d6 | ||
|   | bf7c175ee7 | ||
|   | f84af35ed6 | ||
|   | 99063b3eb1 | ||
|   | 3bec18f28d | ||
|   | 15de7a7894 | ||
|   | e20e04e677 | ||
|   | 5fc6ae2835 | ||
|   | 7d281b8c96 | ||
|   | 4880b801a7 | ||
|   | 74e354456a | ||
|   | af2e03aa36 | ||
|   | d8fa660ab6 | ||
|   | 1a62d48297 | ||
|   | 7ba01be13d | ||
|   | 1a83d64db7 | ||
|   | 5b53014c40 | ||
|   | 83685340af | ||
|   | 31e0cc4dec | ||
|   | 56b87fc1f5 | ||
|   | 6b956a2dd7 | ||
|   | 1937623d7d | ||
|   | 3b60b10945 | ||
|   | 7173acd350 | ||
|   | 6310d87338 | ||
|   | 49a1ed7c18 | ||
|   | d426e280d9 | ||
|   | 6154fb29f1 | ||
|   | 97d48ef9d6 | ||
|   | 88992625c4 | ||
|   | bc6eb44218 | ||
|   | cf9ccd799d | ||
|   | ffa0e4e771 | ||
|   | 60fa9c196c | ||
|   | df860d22fb | ||
|   | cb46ff326c | ||
|   | f277a853ef | ||
|   | 9ae34f67c3 | ||
|   | c9223218cc | ||
|   | c0dd645aba | ||
|   | 2e948eb5b6 | ||
|   | c3276889cf | ||
|   | a76ca8282d | ||
|   | 8ce6b8362f | ||
|   | 842fb12f05 | ||
|   | d63e1511af | ||
|   | 278783b8e0 | ||
|   | d24e3c922d | ||
|   | 1d02cd2283 | ||
|   | 8edeb82a87 | ||
|   | 146e9279de | ||
|   | 47105f50a9 | ||
|   | 16c9c80f37 | ||
|   | 8e7e4bc95a | ||
|   | 0aa3d2f930 | ||
|   | ce77755a1e | ||
|   | 0f31f20c87 | ||
|   | ee6da2aaa5 | ||
|   | a35f087cd9 | ||
|   | 6e029b44dd | ||
|   | 973c0cff34 | ||
|   | 2027eea6ac | ||
|   | 2f43692f33 | ||
|   | 6d24992f88 | ||
|   | b4388a58d6 | ||
|   | 158aa05fac | ||
|   | f2731bf55e | ||
|   | 7304e99fce | ||
|   | 02700b83eb | ||
|   | 676b25acf9 | ||
|   | 556359ea2d | ||
|   | b72923e0f5 | ||
|   | 115ac9f75e | ||
|   | 32e36f6708 | ||
|   | d949b7a4f9 | ||
|   | eae1171ff5 | ||
|   | 76a1b75a51 | ||
|   | 8882c0daea | ||
|   | 07ebc16d59 | ||
|   | 0ceb109964 | ||
|   | 118b0d0038 | ||
|   | 5e87067792 | ||
|   | c946a252e8 | ||
|   | f9ad2ba1dd | ||
|   | 0d0ecd33bd | ||
|   | e4b98fd05b | ||
|   | 95a5933303 | ||
|   | da3b55fa64 | ||
|   | fbbabfb90e | ||
|   | f13da6830d | ||
|   | f560a8e2f8 | ||
|   | 56f1139c2f | ||
|   | 773bdfc1e2 | ||
|   | f449666628 | ||
|   | 3f282de0ab | ||
|   | 440dd8d22f | ||
|   | dcff9de2f7 | ||
|   | a192866543 | ||
|   | 10081416de | ||
|   | e2bed618f9 | ||
|   | 03ab1f3823 | ||
|   | ac8aeb63d9 | ||
|   | 2e16d822fa | ||
|   | e407d873fa | ||
|   | fd712a1dbe | ||
|   | e9028b40ce | ||
|   | c9da3dee7c | ||
|   | c8c224e202 | ||
|   | f34559daaf | ||
|   | 9fefbf4c27 | ||
|   | 1af9fd73ea | ||
|   | 75ef394eff | ||
|   | ec6cc2c63e | ||
|   | 06bc2e192b | ||
|   | 78701ec7c1 | ||
|   | c925fab7e4 | ||
|   | 42fd72c164 | ||
|   | 7fd160e1a2 | ||
|   | 97a0d940eb | ||
|   | efaa099d81 | ||
|   | 47864a804b | ||
|   | 91136c0e43 | ||
|   | 28c3b1bd61 | ||
|   | 551352bc40 | ||
|   | e73c24c925 | ||
|   | 7ec4c286cc | ||
|   | 6705e2ec4b | ||
|   | 6f0373063b | ||
|   | f64eef60b5 | ||
|   | 89546bf86b | ||
|   | 793678feca | ||
|   | 923cc3019a | ||
|   | 10eb98a5f6 | ||
|   | bd9e89d8dd | ||
|   | 1926b4ce73 | ||
|   | 4ef3062d74 | ||
|   | abb6e0f60f | ||
|   | f204d8d84e | ||
|   | fa301656f1 | ||
|   | 7e1221028f | ||
|   | 41308cb2dd | ||
|   | 130600521c | ||
|   | cd57548a48 | ||
|   | efacc99f76 | ||
|   | f0d236e172 | ||
|   | a8118bd8c6 | ||
|   | 0e58f2ef53 | ||
|   | f4b22b3a0c | ||
|   | df5bd281c7 | ||
|   | a3f23837ce | ||
|   | 612d989b97 | ||
|   | 42c01ee9a2 | ||
|   | 14074db591 | ||
|   | 43dfdd7942 | ||
|   | f397b97ccf | ||
|   | 95f8716144 | ||
|   | 17ba472b2e | ||
|   | 42d82571ab | ||
|   | 9119a28141 | ||
|   | a32263d838 | ||
|   | 208ae2bb88 | ||
|   | 4d85462a85 | ||
|   | f601aa9ca0 | ||
|   | 8aee3ad455 | ||
|   | 6a2a1e9561 | ||
|   | 5f8786c9dc | ||
|   | 73f1d3eead | ||
|   | 2bf21bb3c3 | ||
|   | f80f0dbb11 | ||
|   | 37518c70c4 | ||
|   | e5951b5bef | ||
|   | ab320bd90b | ||
|   | 7bd36b5371 | ||
|   | b882b0f2bc | ||
|   | 38d7ae73cc | ||
|   | 4527c6ee5d | ||
|   | 85829e70c1 | ||
|   | 256c08d82a | ||
|   | c2ce03c047 | ||
|   | f2af19e198 | ||
|   | 930b7c092d | ||
|   | 00757c69c6 | ||
|   | 55f267d0fc | ||
|   | 6b96aff6e8 | ||
|   | 32b773a8fa | ||
|   | 03089adad6 | ||
|   | 4a1fe746ab | ||
|   | aa52c05d2c | ||
|   | 26407a43e7 | ||
|   | a02934bf19 | ||
|   | 09c65fba09 | ||
|   | 4305c727d0 | ||
|   | 188339897f | ||
|   | 4ecff9a707 | ||
|   | 355aed49c6 | ||
|   | 4717b6b0f0 | ||
|   | 45ebe9048d | ||
|   | b2170c49a3 | ||
|   | dc2f4d6115 | ||
|   | 1eb132440f | ||
|   | a464bbc37a | ||
|   | ed995697c2 | ||
|   | 163cd84c7b | ||
|   | 293d7cc292 | ||
|   | 5de1b4e74c | ||
|   | 7b474975da | ||
|   | beab51516b | ||
|   | fe8685a50c | ||
|   | f9af5d0885 | ||
|   | e8136a9720 | ||
|   | 531e5d4556 | ||
|   | e66255963a | ||
|   | 246aac8ee4 | ||
|   | 23cfeff685 | ||
|   | a5e7e0d126 | ||
|   | 5bebc30ba0 | ||
|   | 0e7057f5b9 | ||
|   | 7c6c365ba4 | ||
|   | 424c9bb0c5 | ||
|   | 9d0f26594c | ||
|   | 99c17de079 | ||
|   | b1e3dd0af6 | ||
|   | 261cb89530 | ||
|   | ff6773ba37 | ||
|   | bdfbbfcbbd | ||
|   | 0c4cd56758 | ||
|   | 4a36658321 | ||
|   | 7aae938685 | ||
|   | 3723401e7a | ||
|   | 70631366a9 | ||
|   | 0e40bbda3e | ||
|   | e9aa475398 | ||
|   | 8d2a811184 | ||
|   | dd7f5b6700 | ||
|   | a4f6277737 | ||
|   | c2bfaacbb7 | ||
|   | a17cbfa2d4 | ||
|   | fb9a101555 | ||
|   | e319cf0200 | ||
|   | 0a8395ef6a | ||
|   | 38df5e01be | ||
|   | ebd891a868 | ||
|   | 4ab2395cbe | ||
|   | 5f1f989fc9 | ||
|   | 44b709eee3 | ||
|   | d0d7726597 | ||
|   | 054c342aeb | ||
|   | c79c33baf7 | ||
|   | 23b00e35b2 | ||
|   | fe51079266 | ||
|   | 0791b0bbee | ||
|   | dbf04c8eeb | ||
|   | 6204256df8 | ||
|   | 93cc8c2327 | ||
|   | 68a2e5bbbc | ||
|   | 72792153f2 | ||
|   | 88b6ef1897 | 
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -362,8 +362,5 @@ MigrationBackup/ | |||||||
| # Fody - auto-generated XML schema | # Fody - auto-generated XML schema | ||||||
| FodyWeavers.xsd | FodyWeavers.xsd | ||||||
|  |  | ||||||
| /src/*Pro*/ |  | ||||||
| /src/*Pro* | /framework/*Pro* | ||||||
| /src/*pro* |  | ||||||
| /src/*pro*/ |  | ||||||
| /src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json |  | ||||||
|   | |||||||
							
								
								
									
										202
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										202
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,202 +0,0 @@ | |||||||
|  |  | ||||||
|                                  Apache License |  | ||||||
|                            Version 2.0, January 2004 |  | ||||||
|                         http://www.apache.org/licenses/ |  | ||||||
|  |  | ||||||
|    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |  | ||||||
|  |  | ||||||
|    1. Definitions. |  | ||||||
|  |  | ||||||
|       "License" shall mean the terms and conditions for use, reproduction, |  | ||||||
|       and distribution as defined by Sections 1 through 9 of this document. |  | ||||||
|  |  | ||||||
|       "Licensor" shall mean the copyright owner or entity authorized by |  | ||||||
|       the copyright owner that is granting the License. |  | ||||||
|  |  | ||||||
|       "Legal Entity" shall mean the union of the acting entity and all |  | ||||||
|       other entities that control, are controlled by, or are under common |  | ||||||
|       control with that entity. For the purposes of this definition, |  | ||||||
|       "control" means (i) the power, direct or indirect, to cause the |  | ||||||
|       direction or management of such entity, whether by contract or |  | ||||||
|       otherwise, or (ii) ownership of fifty percent (50%) or more of the |  | ||||||
|       outstanding shares, or (iii) beneficial ownership of such entity. |  | ||||||
|  |  | ||||||
|       "You" (or "Your") shall mean an individual or Legal Entity |  | ||||||
|       exercising permissions granted by this License. |  | ||||||
|  |  | ||||||
|       "Source" form shall mean the preferred form for making modifications, |  | ||||||
|       including but not limited to software source code, documentation |  | ||||||
|       source, and configuration files. |  | ||||||
|  |  | ||||||
|       "Object" form shall mean any form resulting from mechanical |  | ||||||
|       transformation or translation of a Source form, including but |  | ||||||
|       not limited to compiled object code, generated documentation, |  | ||||||
|       and conversions to other media types. |  | ||||||
|  |  | ||||||
|       "Work" shall mean the work of authorship, whether in Source or |  | ||||||
|       Object form, made available under the License, as indicated by a |  | ||||||
|       copyright notice that is included in or attached to the work |  | ||||||
|       (an example is provided in the Appendix below). |  | ||||||
|  |  | ||||||
|       "Derivative Works" shall mean any work, whether in Source or Object |  | ||||||
|       form, that is based on (or derived from) the Work and for which the |  | ||||||
|       editorial revisions, annotations, elaborations, or other modifications |  | ||||||
|       represent, as a whole, an original work of authorship. For the purposes |  | ||||||
|       of this License, Derivative Works shall not include works that remain |  | ||||||
|       separable from, or merely link (or bind by name) to the interfaces of, |  | ||||||
|       the Work and Derivative Works thereof. |  | ||||||
|  |  | ||||||
|       "Contribution" shall mean any work of authorship, including |  | ||||||
|       the original version of the Work and any modifications or additions |  | ||||||
|       to that Work or Derivative Works thereof, that is intentionally |  | ||||||
|       submitted to Licensor for inclusion in the Work by the copyright owner |  | ||||||
|       or by an individual or Legal Entity authorized to submit on behalf of |  | ||||||
|       the copyright owner. For the purposes of this definition, "submitted" |  | ||||||
|       means any form of electronic, verbal, or written communication sent |  | ||||||
|       to the Licensor or its representatives, including but not limited to |  | ||||||
|       communication on electronic mailing lists, source code control systems, |  | ||||||
|       and issue tracking systems that are managed by, or on behalf of, the |  | ||||||
|       Licensor for the purpose of discussing and improving the Work, but |  | ||||||
|       excluding communication that is conspicuously marked or otherwise |  | ||||||
|       designated in writing by the copyright owner as "Not a Contribution." |  | ||||||
|  |  | ||||||
|       "Contributor" shall mean Licensor and any individual or Legal Entity |  | ||||||
|       on behalf of whom a Contribution has been received by Licensor and |  | ||||||
|       subsequently incorporated within the Work. |  | ||||||
|  |  | ||||||
|    2. Grant of Copyright License. Subject to the terms and conditions of |  | ||||||
|       this License, each Contributor hereby grants to You a perpetual, |  | ||||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable |  | ||||||
|       copyright license to reproduce, prepare Derivative Works of, |  | ||||||
|       publicly display, publicly perform, sublicense, and distribute the |  | ||||||
|       Work and such Derivative Works in Source or Object form. |  | ||||||
|  |  | ||||||
|    3. Grant of Patent License. Subject to the terms and conditions of |  | ||||||
|       this License, each Contributor hereby grants to You a perpetual, |  | ||||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable |  | ||||||
|       (except as stated in this section) patent license to make, have made, |  | ||||||
|       use, offer to sell, sell, import, and otherwise transfer the Work, |  | ||||||
|       where such license applies only to those patent claims licensable |  | ||||||
|       by such Contributor that are necessarily infringed by their |  | ||||||
|       Contribution(s) alone or by combination of their Contribution(s) |  | ||||||
|       with the Work to which such Contribution(s) was submitted. If You |  | ||||||
|       institute patent litigation against any entity (including a |  | ||||||
|       cross-claim or counterclaim in a lawsuit) alleging that the Work |  | ||||||
|       or a Contribution incorporated within the Work constitutes direct |  | ||||||
|       or contributory patent infringement, then any patent licenses |  | ||||||
|       granted to You under this License for that Work shall terminate |  | ||||||
|       as of the date such litigation is filed. |  | ||||||
|  |  | ||||||
|    4. Cachetribution. You may reproduce and distribute copies of the |  | ||||||
|       Work or Derivative Works thereof in any medium, with or without |  | ||||||
|       modifications, and in Source or Object form, provided that You |  | ||||||
|       meet the following conditions: |  | ||||||
|  |  | ||||||
|       (a) You must give any other recipients of the Work or |  | ||||||
|           Derivative Works a copy of this License; and |  | ||||||
|  |  | ||||||
|       (b) You must cause any modified files to carry prominent notices |  | ||||||
|           stating that You changed the files; and |  | ||||||
|  |  | ||||||
|       (c) You must retain, in the Source form of any Derivative Works |  | ||||||
|           that You distribute, all copyright, patent, trademark, and |  | ||||||
|           attribution notices from the Source form of the Work, |  | ||||||
|           excluding those notices that do not pertain to any part of |  | ||||||
|           the Derivative Works; and |  | ||||||
|  |  | ||||||
|       (d) If the Work includes a "NOTICE" text file as part of its |  | ||||||
|           distribution, then any Derivative Works that You distribute must |  | ||||||
|           include a readable copy of the attribution notices contained |  | ||||||
|           within such NOTICE file, excluding those notices that do not |  | ||||||
|           pertain to any part of the Derivative Works, in at least one |  | ||||||
|           of the following places: within a NOTICE text file distributed |  | ||||||
|           as part of the Derivative Works; within the Source form or |  | ||||||
|           documentation, if provided along with the Derivative Works; or, |  | ||||||
|           within a display generated by the Derivative Works, if and |  | ||||||
|           wherever such third-party notices normally appear. The contents |  | ||||||
|           of the NOTICE file are for informational purposes only and |  | ||||||
|           do not modify the License. You may add Your own attribution |  | ||||||
|           notices within Derivative Works that You distribute, alongside |  | ||||||
|           or as an addendum to the NOTICE text from the Work, provided |  | ||||||
|           that such additional attribution notices cannot be construed |  | ||||||
|           as modifying the License. |  | ||||||
|  |  | ||||||
|       You may add Your own copyright statement to Your modifications and |  | ||||||
|       may provide additional or different license terms and conditions |  | ||||||
|       for use, reproduction, or distribution of Your modifications, or |  | ||||||
|       for any such Derivative Works as a whole, provided Your use, |  | ||||||
|       reproduction, and distribution of the Work otherwise complies with |  | ||||||
|       the conditions stated in this License. |  | ||||||
|  |  | ||||||
|    5. Submission of Contributions. Unless You explicitly state otherwise, |  | ||||||
|       any Contribution intentionally submitted for inclusion in the Work |  | ||||||
|       by You to the Licensor shall be under the terms and conditions of |  | ||||||
|       this License, without any additional terms or conditions. |  | ||||||
|       Notwithstanding the above, nothing herein shall supersede or modify |  | ||||||
|       the terms of any separate license agreement you may have executed |  | ||||||
|       with Licensor regarding such Contributions. |  | ||||||
|  |  | ||||||
|    6. Trademarks. This License does not grant permission to use the trade |  | ||||||
|       names, trademarks, service marks, or product names of the Licensor, |  | ||||||
|       except as required for reasonable and customary use in describing the |  | ||||||
|       origin of the Work and reproducing the content of the NOTICE file. |  | ||||||
|  |  | ||||||
|    7. Disclaimer of Warranty. Unless required by applicable law or |  | ||||||
|       agreed to in writing, Licensor provides the Work (and each |  | ||||||
|       Contributor provides its Contributions) on an "AS IS" BASIS, |  | ||||||
|       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |  | ||||||
|       implied, including, without limitation, any warranties or conditions |  | ||||||
|       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |  | ||||||
|       PARTICULAR PURPOSE. You are solely responsible for determining the |  | ||||||
|       appropriateness of using or redistributing the Work and assume any |  | ||||||
|       risks associated with Your exercise of permissions under this License. |  | ||||||
|  |  | ||||||
|    8. Limitation of Liability. In no event and under no legal theory, |  | ||||||
|       whether in tort (including negligence), contract, or otherwise, |  | ||||||
|       unless required by applicable law (such as deliberate and grossly |  | ||||||
|       negligent acts) or agreed to in writing, shall any Contributor be |  | ||||||
|       liable to You for damages, including any direct, indirect, special, |  | ||||||
|       incidental, or consequential damages of any character arising as a |  | ||||||
|       result of this License or out of the use or inability to use the |  | ||||||
|       Work (including but not limited to damages for loss of goodwill, |  | ||||||
|       work stoppage, computer failure or malfunction, or any and all |  | ||||||
|       other commercial damages or losses), even if such Contributor |  | ||||||
|       has been advised of the possibility of such damages. |  | ||||||
|  |  | ||||||
|    9. Accepting Warranty or Additional Liability. While redistributing |  | ||||||
|       the Work or Derivative Works thereof, You may choose to offer, |  | ||||||
|       and charge a fee for, acceptance of support, warranty, indemnity, |  | ||||||
|       or other liability obligations and/or rights consistent with this |  | ||||||
|       License. However, in accepting such obligations, You may act only |  | ||||||
|       on Your own behalf and on Your sole responsibility, not on behalf |  | ||||||
|       of any other Contributor, and only if You agree to indemnify, |  | ||||||
|       defend, and hold each Contributor harmless for any liability |  | ||||||
|       incurred by, or claims asserted against, such Contributor by reason |  | ||||||
|       of your accepting any such warranty or additional liability. |  | ||||||
|  |  | ||||||
|    END OF TERMS AND CONDITIONS |  | ||||||
|  |  | ||||||
|    APPENDIX: How to apply the Apache License to your work. |  | ||||||
|  |  | ||||||
|       To apply the Apache License to your work, attach the following |  | ||||||
|       boilerplate notice, with the fields enclosed by brackets "[]" |  | ||||||
|       replaced with your own identifying information. (Don't include |  | ||||||
|       the brackets!)  The text should be enclosed in the appropriate |  | ||||||
|       comment syntax for the file format. We also recommend that a |  | ||||||
|       file or class name and description of purpose be included on the |  | ||||||
|       same "printed page" as the copyright notice for easier |  | ||||||
|       identification within third-party archives. |  | ||||||
|  |  | ||||||
|    Copyright 2023-present Diego |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
							
								
								
									
										202
									
								
								LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | |||||||
|  |  | ||||||
|  |                                  Apache License | ||||||
|  |                            Version 2.0, January 2004 | ||||||
|  |                         http://www.apache.org/licenses/ | ||||||
|  |  | ||||||
|  |    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||||||
|  |  | ||||||
|  |    1. Definitions. | ||||||
|  |  | ||||||
|  |       "License" shall mean the terms and conditions for use, reproduction, | ||||||
|  |       and distribution as defined by Sections 1 through 9 of this document. | ||||||
|  |  | ||||||
|  |       "Licensor" shall mean the copyright owner or entity authorized by | ||||||
|  |       the copyright owner that is granting the License. | ||||||
|  |  | ||||||
|  |       "Legal Entity" shall mean the union of the acting entity and all | ||||||
|  |       other entities that control, are controlled by, or are under common | ||||||
|  |       control with that entity. For the purposes of this definition, | ||||||
|  |       "control" means (i) the power, direct or indirect, to cause the | ||||||
|  |       direction or management of such entity, whether by contract or | ||||||
|  |       otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||||
|  |       outstanding shares, or (iii) beneficial ownership of such entity. | ||||||
|  |  | ||||||
|  |       "You" (or "Your") shall mean an individual or Legal Entity | ||||||
|  |       exercising permissions granted by this License. | ||||||
|  |  | ||||||
|  |       "Source" form shall mean the preferred form for making modifications, | ||||||
|  |       including but not limited to software source code, documentation | ||||||
|  |       source, and configuration files. | ||||||
|  |  | ||||||
|  |       "Object" form shall mean any form resulting from mechanical | ||||||
|  |       transformation or translation of a Source form, including but | ||||||
|  |       not limited to compiled object code, generated documentation, | ||||||
|  |       and conversions to other media types. | ||||||
|  |  | ||||||
|  |       "Work" shall mean the work of authorship, whether in Source or | ||||||
|  |       Object form, made available under the License, as indicated by a | ||||||
|  |       copyright notice that is included in or attached to the work | ||||||
|  |       (an example is provided in the Appendix below). | ||||||
|  |  | ||||||
|  |       "Derivative Works" shall mean any work, whether in Source or Object | ||||||
|  |       form, that is based on (or derived from) the Work and for which the | ||||||
|  |       editorial revisions, annotations, elaborations, or other modifications | ||||||
|  |       represent, as a whole, an original work of authorship. For the purposes | ||||||
|  |       of this License, Derivative Works shall not include works that remain | ||||||
|  |       separable from, or merely link (or bind by name) to the interfaces of, | ||||||
|  |       the Work and Derivative Works thereof. | ||||||
|  |  | ||||||
|  |       "Contribution" shall mean any work of authorship, including | ||||||
|  |       the original version of the Work and any modifications or additions | ||||||
|  |       to that Work or Derivative Works thereof, that is intentionally | ||||||
|  |       submitted to Licensor for inclusion in the Work by the copyright owner | ||||||
|  |       or by an individual or Legal Entity authorized to submit on behalf of | ||||||
|  |       the copyright owner. For the purposes of this definition, "submitted" | ||||||
|  |       means any form of electronic, verbal, or written communication sent | ||||||
|  |       to the Licensor or its representatives, including but not limited to | ||||||
|  |       communication on electronic mailing lists, source code control systems, | ||||||
|  |       and issue tracking systems that are managed by, or on behalf of, the | ||||||
|  |       Licensor for the purpose of discussing and improving the Work, but | ||||||
|  |       excluding communication that is conspicuously marked or otherwise | ||||||
|  |       designated in writing by the copyright owner as "Not a Contribution." | ||||||
|  |  | ||||||
|  |       "Contributor" shall mean Licensor and any individual or Legal Entity | ||||||
|  |       on behalf of whom a Contribution has been received by Licensor and | ||||||
|  |       subsequently incorporated within the Work. | ||||||
|  |  | ||||||
|  |    2. Grant of Copyright License. Subject to the terms and conditions of | ||||||
|  |       this License, each Contributor hereby grants to You a perpetual, | ||||||
|  |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||||
|  |       copyright license to reproduce, prepare Derivative Works of, | ||||||
|  |       publicly display, publicly perform, sublicense, and distribute the | ||||||
|  |       Work and such Derivative Works in Source or Object form. | ||||||
|  |  | ||||||
|  |    3. Grant of Patent License. Subject to the terms and conditions of | ||||||
|  |       this License, each Contributor hereby grants to You a perpetual, | ||||||
|  |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||||
|  |       (except as stated in this section) patent license to make, have made, | ||||||
|  |       use, offer to sell, sell, import, and otherwise transfer the Work, | ||||||
|  |       where such license applies only to those patent claims licensable | ||||||
|  |       by such Contributor that are necessarily infringed by their | ||||||
|  |       Contribution(s) alone or by combination of their Contribution(s) | ||||||
|  |       with the Work to which such Contribution(s) was submitted. If You | ||||||
|  |       institute patent litigation against any entity (including a | ||||||
|  |       cross-claim or counterclaim in a lawsuit) alleging that the Work | ||||||
|  |       or a Contribution incorporated within the Work constitutes direct | ||||||
|  |       or contributory patent infringement, then any patent licenses | ||||||
|  |       granted to You under this License for that Work shall terminate | ||||||
|  |       as of the date such litigation is filed. | ||||||
|  |  | ||||||
|  |    4. Redistribution. You may reproduce and distribute copies of the | ||||||
|  |       Work or Derivative Works thereof in any medium, with or without | ||||||
|  |       modifications, and in Source or Object form, provided that You | ||||||
|  |       meet the following conditions: | ||||||
|  |  | ||||||
|  |       (a) You must give any other recipients of the Work or | ||||||
|  |           Derivative Works a copy of this License; and | ||||||
|  |  | ||||||
|  |       (b) You must cause any modified files to carry prominent notices | ||||||
|  |           stating that You changed the files; and | ||||||
|  |  | ||||||
|  |       (c) You must retain, in the Source form of any Derivative Works | ||||||
|  |           that You distribute, all copyright, patent, trademark, and | ||||||
|  |           attribution notices from the Source form of the Work, | ||||||
|  |           excluding those notices that do not pertain to any part of | ||||||
|  |           the Derivative Works; and | ||||||
|  |  | ||||||
|  |       (d) If the Work includes a "NOTICE" text file as part of its | ||||||
|  |           distribution, then any Derivative Works that You distribute must | ||||||
|  |           include a readable copy of the attribution notices contained | ||||||
|  |           within such NOTICE file, excluding those notices that do not | ||||||
|  |           pertain to any part of the Derivative Works, in at least one | ||||||
|  |           of the following places: within a NOTICE text file distributed | ||||||
|  |           as part of the Derivative Works; within the Source form or | ||||||
|  |           documentation, if provided along with the Derivative Works; or, | ||||||
|  |           within a display generated by the Derivative Works, if and | ||||||
|  |           wherever such third-party notices normally appear. The contents | ||||||
|  |           of the NOTICE file are for informational purposes only and | ||||||
|  |           do not modify the License. You may add Your own attribution | ||||||
|  |           notices within Derivative Works that You distribute, alongside | ||||||
|  |           or as an addendum to the NOTICE text from the Work, provided | ||||||
|  |           that such additional attribution notices cannot be construed | ||||||
|  |           as modifying the License. | ||||||
|  |  | ||||||
|  |       You may add Your own copyright statement to Your modifications and | ||||||
|  |       may provide additional or different license terms and conditions | ||||||
|  |       for use, reproduction, or distribution of Your modifications, or | ||||||
|  |       for any such Derivative Works as a whole, provided Your use, | ||||||
|  |       reproduction, and distribution of the Work otherwise complies with | ||||||
|  |       the conditions stated in this License. | ||||||
|  |  | ||||||
|  |    5. Submission of Contributions. Unless You explicitly state otherwise, | ||||||
|  |       any Contribution intentionally submitted for inclusion in the Work | ||||||
|  |       by You to the Licensor shall be under the terms and conditions of | ||||||
|  |       this License, without any additional terms or conditions. | ||||||
|  |       Notwithstanding the above, nothing herein shall supersede or modify | ||||||
|  |       the terms of any separate license agreement you may have executed | ||||||
|  |       with Licensor regarding such Contributions. | ||||||
|  |  | ||||||
|  |    6. Trademarks. This License does not grant permission to use the trade | ||||||
|  |       names, trademarks, service marks, or product names of the Licensor, | ||||||
|  |       except as required for reasonable and customary use in describing the | ||||||
|  |       origin of the Work and reproducing the content of the NOTICE file. | ||||||
|  |  | ||||||
|  |    7. Disclaimer of Warranty. Unless required by applicable law or | ||||||
|  |       agreed to in writing, Licensor provides the Work (and each | ||||||
|  |       Contributor provides its Contributions) on an "AS IS" BASIS, | ||||||
|  |       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||||||
|  |       implied, including, without limitation, any warranties or conditions | ||||||
|  |       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||||||
|  |       PARTICULAR PURPOSE. You are solely responsible for determining the | ||||||
|  |       appropriateness of using or redistributing the Work and assume any | ||||||
|  |       risks associated with Your exercise of permissions under this License. | ||||||
|  |  | ||||||
|  |    8. Limitation of Liability. In no event and under no legal theory, | ||||||
|  |       whether in tort (including negligence), contract, or otherwise, | ||||||
|  |       unless required by applicable law (such as deliberate and grossly | ||||||
|  |       negligent acts) or agreed to in writing, shall any Contributor be | ||||||
|  |       liable to You for damages, including any direct, indirect, special, | ||||||
|  |       incidental, or consequential damages of any character arising as a | ||||||
|  |       result of this License or out of the use or inability to use the | ||||||
|  |       Work (including but not limited to damages for loss of goodwill, | ||||||
|  |       work stoppage, computer failure or malfunction, or any and all | ||||||
|  |       other commercial damages or losses), even if such Contributor | ||||||
|  |       has been advised of the possibility of such damages. | ||||||
|  |  | ||||||
|  |    9. Accepting Warranty or Additional Liability. While redistributing | ||||||
|  |       the Work or Derivative Works thereof, You may choose to offer, | ||||||
|  |       and charge a fee for, acceptance of support, warranty, indemnity, | ||||||
|  |       or other liability obligations and/or rights consistent with this | ||||||
|  |       License. However, in accepting such obligations, You may act only | ||||||
|  |       on Your own behalf and on Your sole responsibility, not on behalf | ||||||
|  |       of any other Contributor, and only if You agree to indemnify, | ||||||
|  |       defend, and hold each Contributor harmless for any liability | ||||||
|  |       incurred by, or claims asserted against, such Contributor by reason | ||||||
|  |       of your accepting any such warranty or additional liability. | ||||||
|  |  | ||||||
|  |    END OF TERMS AND CONDITIONS | ||||||
|  |  | ||||||
|  |    APPENDIX: How to apply the Apache License to your work. | ||||||
|  |  | ||||||
|  |       To apply the Apache License to your work, attach the following | ||||||
|  |       boilerplate notice, with the fields enclosed by brackets "[]" | ||||||
|  |       replaced with your own identifying information. (Don't include | ||||||
|  |       the brackets!)  The text should be enclosed in the appropriate | ||||||
|  |       comment syntax for the file format. We also recommend that a | ||||||
|  |       file or class name and description of purpose be included on the | ||||||
|  |       same "printed page" as the copyright notice for easier | ||||||
|  |       identification within third-party archives. | ||||||
|  |  | ||||||
|  |    Copyright 2023-present Diego | ||||||
|  |  | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  |  | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
							
								
								
									
										103
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,101 +1,38 @@ | |||||||
|  |  | ||||||
| # ThingsGateway | # ThingsGateway | ||||||
|  |  | ||||||
|  | ## 介绍 | ||||||
|  |  | ||||||
| ## Introduction |  **NetCore** 跨平台边缘采集网关(工业设备采集) | ||||||
|  |  | ||||||
|  |  **ThingsGateway** 存储库同时提供 [**设备采集驱动**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22) | ||||||
| A cross-platform, high-performance edge data collection gateway based on net9. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Documentation |  **ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 [**ThingsGateway.Admin**](https://gitee.com/dotnetchina/ThingsGateway/blob/master/framework/ThingsGateway.Admin.sln) | ||||||
|  |  | ||||||
|  |  | ||||||
| [Documentation](https://thingsgateway.cn/). |  | ||||||
|  |  | ||||||
| [NuGet](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Demo | ## 文档 | ||||||
|  |  | ||||||
|  | [ThingsGateway](https://diego2098.gitee.io/thingsgateway-docs/) 文档。 | ||||||
| [Demo](https://demo.thingsgateway.cn/) |  | ||||||
|  |  | ||||||
|  | ## 协议 | ||||||
| Account: **SuperAdmin** |  | ||||||
|  |  | ||||||
|  | [ThingsGateway](https://gitee.com/diego2098/ThingsGateway) 采用 [Apache-2.0](https://gitee.com/diego2098/ThingsGateway/blob/master/LICENSE.zh) 开源协议。 | ||||||
| Password: **111111** |  | ||||||
|  |  | ||||||
|  | ## 演示 | ||||||
| **In the upper-right corner, switch to the IoT Gateway module in the personal popup box** |  | ||||||
|  |  | ||||||
| ## Docker | [ThingsGateway演示地址](http://120.24.62.140:5000/) | ||||||
|  |  | ||||||
| ```shell | 账户	:  **superAdmin**	 | ||||||
|  |  | ||||||
| docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway | 密码 : **111111** | ||||||
|  |  | ||||||
| docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64 | ## 赞助 | ||||||
| ``` |  | ||||||
|  | [ThingsGateway赞助途径](https://diego2098.gitee.io/thingsgateway-docs/docs/donate) | ||||||
|  |  | ||||||
|  | ## 社区 | ||||||
|  |  | ||||||
|  | QQ群:605534569 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ### Plugin List |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| #### Data Collection Plugins |  | ||||||
|  |  | ||||||
|  |  | ||||||
| | Plugin Name | Remarks                                                       | |  | ||||||
| | ----------- | ------------------------------------------------------------- | |  | ||||||
| | Modbus      | Supports Rtu/Tcp message formats, with Serial/Tcp/Udp links   | |  | ||||||
| | SiemensS7   | Siemens PLC S7 series                                         | |  | ||||||
| | Dlt6452007  | Supports Serial/Tcp/Udp links                                 | |  | ||||||
| | OpcDaMaster | Compiled for 64-bit                                           | |  | ||||||
| | OpcUaMaster | Supports certificate login, object extension, Json read/write | |  | ||||||
| | Webhook          | Webhook                                             | |  | ||||||
|  |  | ||||||
| #### Business Plugins |  | ||||||
|  |  | ||||||
|  |  | ||||||
| | Plugin Name      | Remarks                                                                                           | |  | ||||||
| | ---------------- | ------------------------------------------------------------------------------------------------- | |  | ||||||
| | ModbusSlave      | Supports Rtu/Tcp message formats, with Serial/Tcp/Udp links, supports Rpc reverse writing         | |  | ||||||
| | OpcUaServer      | OpcUa server, supports Rpc reverse writing                                                        | |  | ||||||
| | MqttClient       | Mqtt client, supports Rpc reverse writing, script-customizable upload content                     | |  | ||||||
| | MqttServer       | Mqtt server, supports WebSocket, supports Rpc reverse writing, script-customizable upload content | |  | ||||||
| | KafkaProducer    | Script-customizable upload content                                                                | |  | ||||||
| | RabbitMQProducer | Script-customizable upload content                                                                | |  | ||||||
| | SqlDB            | Relational database storage, supports historical storage and real-time data updates               | |  | ||||||
| | SqlHistoryAlarm      | Alarm historical data relational database storage                                                 | |  | ||||||
| | TDengineDB       | Time-series database storage                                                                      | |  | ||||||
| | QuestDB          | Time-series database storage                                                                      | |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## License |  | ||||||
|  |  | ||||||
|  |  | ||||||
| [License](https://thingsgateway.cn/docs/1) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Sponsorship |  | ||||||
|  |  | ||||||
|  |  | ||||||
| [Sponsorship Approach](https://thingsgateway.cn/docs/1000) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Community |  | ||||||
|  |  | ||||||
|  |  | ||||||
| QQ Group: 605534569 [Jump](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Pro Plugins |  | ||||||
|  |  | ||||||
|  |  | ||||||
| [Plugin List](https://thingsgateway.cn/docs/1001) |  | ||||||
|   | |||||||
| @@ -1,86 +0,0 @@ | |||||||
| # ThingsGateway |  | ||||||
|  |  | ||||||
| ## 介绍 |  | ||||||
|  |  | ||||||
| 基于net9的跨平台高性能边缘采集网关 |  | ||||||
|  |  | ||||||
| ## 文档 |  | ||||||
|  |  | ||||||
| [文档](https://thingsgateway.cn/) |  | ||||||
|  |  | ||||||
| [NuGet](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## 演示 |  | ||||||
|  |  | ||||||
| [ThingsGateway演示地址](https://demo.thingsgateway.cn/) |  | ||||||
|  |  | ||||||
| 账户	:  **SuperAdmin** |  | ||||||
|  |  | ||||||
| 密码 : **111111** |  | ||||||
|  |  | ||||||
| **右上角个人弹出框中,切换到物联网关模块** |  | ||||||
|  |  | ||||||
| ## Docker |  | ||||||
|  |  | ||||||
| ```shell |  | ||||||
|  |  | ||||||
| docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway |  | ||||||
|  |  | ||||||
| docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ### 插件列表 |  | ||||||
|  |  | ||||||
| #### 采集插件 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| | 插件名称    | 备注                                  | |  | ||||||
| | ----------- | ------------------------------------- | |  | ||||||
| | Modbus      | Rtu/Tcp报文格式,支持串口/Tcp/Udp链路 | |  | ||||||
| | SiemensS7   | 西门子PLC S7系列                      | |  | ||||||
| | Dlt6452007  | 支持串口/Tcp/Udp链路                  | |  | ||||||
| | OpcDaMaster | 64位编译                              | |  | ||||||
| | OpcUaMaster | 支持证书登录,扩展对象,Json读写      | |  | ||||||
|  |  | ||||||
| #### 业务插件 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| | 插件名称         | 备注                                                       | |  | ||||||
| | ---------------- | ---------------------------------------------------------- | |  | ||||||
| | ModbusSlave      | Rtu/Tcp报文格式,支持串口/Tcp/Udp链路,支持Rpc反写         | |  | ||||||
| | OpcUaServer      | OpcUa服务端,支持Rpc反写                                   | |  | ||||||
| | MqttClient       | Mqtt客户端,支持Rpc反写,脚本自定义上传内容                | |  | ||||||
| | MqttServer       | Mqtt服务端,支持WebSocket,支持Rpc反写,脚本自定义上传内容 | |  | ||||||
| | KafkaProducer    | 脚本自定义上传内容                                         | |  | ||||||
| | RabbitMQProducer | 脚本自定义上传内容                                         | |  | ||||||
| | SqlDB            | 关系数据库存储,支持历史存储和实时数据更新                 | |  | ||||||
| | SqlHistoryAlarm      | 报警历史数据关系数据库存储                                 | |  | ||||||
| | TDengineDB       | 时序数据库存储                                             | |  | ||||||
| | QuestDB          | 时序数据库存储                                             | |  | ||||||
| | Webhook          | Webhook                                             | |  | ||||||
|  |  | ||||||
| ## 协议 |  | ||||||
|  |  | ||||||
| [版权声明](https://thingsgateway.cn/docs/1) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## 赞助 |  | ||||||
|  |  | ||||||
| [赞助途径](https://thingsgateway.cn/docs/1000) |  | ||||||
|  |  | ||||||
| ## 社区 |  | ||||||
|  |  | ||||||
| QQ群:605534569 [跳转](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569) |  | ||||||
|  |  | ||||||
| ## Pro插件 |  | ||||||
|  |  | ||||||
| [插件列表](https://thingsgateway.cn/docs/1001) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## 特别声明 |  | ||||||
|  |  | ||||||
| ThingsGateway 项目已加入 [dotNET China](https://gitee.com/dotnetchina)  组织。<br/> |  | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										149
									
								
								framework/.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								framework/.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | |||||||
|  | [*.cs] | ||||||
|  |  | ||||||
|  | # CA1822: 将成员标记为 static | ||||||
|  | dotnet_diagnostic.CA1822.severity = none | ||||||
|  |  | ||||||
|  | # CA1816: Dispose 方法应调用 SuppressFinalize | ||||||
|  | dotnet_diagnostic.CA1816.severity = none | ||||||
|  |  | ||||||
|  | # CA2254: 模板应为静态表达式 | ||||||
|  | dotnet_diagnostic.CA2254.severity = none | ||||||
|  |  | ||||||
|  | [*.cs] | ||||||
|  | #### 命名样式 #### | ||||||
|  |  | ||||||
|  | # 命名规则 | ||||||
|  |  | ||||||
|  | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion | ||||||
|  | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface | ||||||
|  | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i | ||||||
|  |  | ||||||
|  | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion | ||||||
|  | dotnet_naming_rule.types_should_be_pascal_case.symbols = types | ||||||
|  | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case | ||||||
|  |  | ||||||
|  | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion | ||||||
|  | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members | ||||||
|  | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case | ||||||
|  |  | ||||||
|  | # 符号规范 | ||||||
|  |  | ||||||
|  | dotnet_naming_symbols.interface.applicable_kinds = interface | ||||||
|  | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected | ||||||
|  | dotnet_naming_symbols.interface.required_modifiers =  | ||||||
|  |  | ||||||
|  | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum | ||||||
|  | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected | ||||||
|  | dotnet_naming_symbols.types.required_modifiers =  | ||||||
|  |  | ||||||
|  | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method | ||||||
|  | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected | ||||||
|  | dotnet_naming_symbols.non_field_members.required_modifiers =  | ||||||
|  |  | ||||||
|  | # 命名样式 | ||||||
|  |  | ||||||
|  | dotnet_naming_style.begins_with_i.required_prefix = I | ||||||
|  | dotnet_naming_style.begins_with_i.required_suffix =  | ||||||
|  | dotnet_naming_style.begins_with_i.word_separator =  | ||||||
|  | dotnet_naming_style.begins_with_i.capitalization = pascal_case | ||||||
|  |  | ||||||
|  | dotnet_naming_style.pascal_case.required_prefix =  | ||||||
|  | dotnet_naming_style.pascal_case.required_suffix =  | ||||||
|  | dotnet_naming_style.pascal_case.word_separator =  | ||||||
|  | dotnet_naming_style.pascal_case.capitalization = pascal_case | ||||||
|  |  | ||||||
|  | dotnet_naming_style.pascal_case.required_prefix =  | ||||||
|  | dotnet_naming_style.pascal_case.required_suffix =  | ||||||
|  | dotnet_naming_style.pascal_case.word_separator =  | ||||||
|  | dotnet_naming_style.pascal_case.capitalization = pascal_case | ||||||
|  | csharp_using_directive_placement = outside_namespace:silent | ||||||
|  | csharp_style_expression_bodied_methods = false:silent | ||||||
|  | csharp_style_expression_bodied_constructors = false:silent | ||||||
|  | csharp_style_expression_bodied_operators = false:silent | ||||||
|  | csharp_style_expression_bodied_properties = true:silent | ||||||
|  | csharp_style_expression_bodied_indexers = true:silent | ||||||
|  | csharp_style_expression_bodied_accessors = true:silent | ||||||
|  | csharp_style_expression_bodied_lambdas = true:silent | ||||||
|  | csharp_style_expression_bodied_local_functions = false:silent | ||||||
|  | csharp_style_conditional_delegate_call = true:suggestion | ||||||
|  | csharp_style_var_for_built_in_types = false:silent | ||||||
|  | csharp_style_var_when_type_is_apparent = false:silent | ||||||
|  | csharp_style_var_elsewhere = false:silent | ||||||
|  | csharp_prefer_simple_using_statement = true:suggestion | ||||||
|  | csharp_prefer_braces = true:silent | ||||||
|  | csharp_style_namespace_declarations = block_scoped:silent | ||||||
|  | csharp_style_prefer_method_group_conversion = true:silent | ||||||
|  | csharp_style_prefer_top_level_statements = true:silent | ||||||
|  |  | ||||||
|  | # CA2208: 正确实例化参数异常 | ||||||
|  | dotnet_diagnostic.CA2208.severity = none | ||||||
|  |  | ||||||
|  | # IDE0057: 使用范围运算符 | ||||||
|  | dotnet_diagnostic.IDE0057.severity = none | ||||||
|  |  | ||||||
|  | # IDE0056: 使用索引运算符 | ||||||
|  | dotnet_diagnostic.IDE0056.severity = none | ||||||
|  |  | ||||||
|  | # CA1830: 最好使用 StringBuilder 的强类型 Append 和 Insert 方法重载 | ||||||
|  | dotnet_diagnostic.CA1830.severity = none | ||||||
|  |  | ||||||
|  | # CA1847: 将字符型文本用于单个字符查找 | ||||||
|  | dotnet_diagnostic.CA1847.severity = none | ||||||
|  |  | ||||||
|  | [*.vb] | ||||||
|  | #### 命名样式 #### | ||||||
|  |  | ||||||
|  | # 命名规则 | ||||||
|  |  | ||||||
|  | dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion | ||||||
|  | dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface | ||||||
|  | dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始 | ||||||
|  |  | ||||||
|  | dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion | ||||||
|  | dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型 | ||||||
|  | dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 | ||||||
|  |  | ||||||
|  | dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion | ||||||
|  | dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员 | ||||||
|  | dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 | ||||||
|  |  | ||||||
|  | # 符号规范 | ||||||
|  |  | ||||||
|  | dotnet_naming_symbols.interface.applicable_kinds = interface | ||||||
|  | dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected | ||||||
|  | dotnet_naming_symbols.interface.required_modifiers =  | ||||||
|  |  | ||||||
|  | dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum | ||||||
|  | dotnet_naming_symbols.类型.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected | ||||||
|  | dotnet_naming_symbols.类型.required_modifiers =  | ||||||
|  |  | ||||||
|  | dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method | ||||||
|  | dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected | ||||||
|  | dotnet_naming_symbols.非字段成员.required_modifiers =  | ||||||
|  |  | ||||||
|  | # 命名样式 | ||||||
|  |  | ||||||
|  | dotnet_naming_style.以_i_开始.required_prefix = I | ||||||
|  | dotnet_naming_style.以_i_开始.required_suffix =  | ||||||
|  | dotnet_naming_style.以_i_开始.word_separator =  | ||||||
|  | dotnet_naming_style.以_i_开始.capitalization = pascal_case | ||||||
|  |  | ||||||
|  | dotnet_naming_style.帕斯卡拼写法.required_prefix =  | ||||||
|  | dotnet_naming_style.帕斯卡拼写法.required_suffix =  | ||||||
|  | dotnet_naming_style.帕斯卡拼写法.word_separator =  | ||||||
|  | dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case | ||||||
|  |  | ||||||
|  | dotnet_naming_style.帕斯卡拼写法.required_prefix =  | ||||||
|  | dotnet_naming_style.帕斯卡拼写法.required_suffix =  | ||||||
|  | dotnet_naming_style.帕斯卡拼写法.word_separator =  | ||||||
|  | dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case | ||||||
|  |  | ||||||
|  | [*.{cs,vb}] | ||||||
|  | dotnet_style_qualification_for_field = false:silent | ||||||
|  | dotnet_style_qualification_for_property = false:silent | ||||||
|  | dotnet_style_qualification_for_method = false:silent | ||||||
|  | dotnet_style_qualification_for_event = false:silent | ||||||
|  | end_of_line = crlf | ||||||
|  |  | ||||||
|  | # IDE0060: 删除未使用的参数 | ||||||
|  | dotnet_diagnostic.IDE0060.severity = none | ||||||
							
								
								
									
										30
									
								
								framework/Directory.Build.props
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								framework/Directory.Build.props
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | <Project> | ||||||
|  | 	<PropertyGroup> | ||||||
|  | 		<TargetFrameworks>net6.0;net7.0</TargetFrameworks> | ||||||
|  | 		<Version>2.1.0.0</Version> | ||||||
|  | 		<Authors>Diego</Authors> | ||||||
|  | 		<Product>ThingsGateway</Product> | ||||||
|  | 		<Copyright>© 2023-present Diego</Copyright> | ||||||
|  | 		<RepositoryUrl>https://gitee.com/diego2098/ThingsGateway</RepositoryUrl> | ||||||
|  | 		<PublishRepositoryUrl>true</PublishRepositoryUrl> | ||||||
|  | 		<EmbedUntrackedSource>true</EmbedUntrackedSource> | ||||||
|  | 		<EmbedAllSources>true</EmbedAllSources> | ||||||
|  | 		<RepositoryType>Gitee</RepositoryType> | ||||||
|  | 		<IncludeSymbols>true</IncludeSymbols> | ||||||
|  | 		<SymbolPackageFormat>snupkg</SymbolPackageFormat> | ||||||
|  | 		<SignAssembly>True</SignAssembly> | ||||||
|  | 		<DelaySign>False</DelaySign> | ||||||
|  | 		<SatelliteResourceLanguages>zh-Hans</SatelliteResourceLanguages> | ||||||
|  | 	</PropertyGroup> | ||||||
|  |  | ||||||
|  | 	<PropertyGroup> | ||||||
|  | 		<DocumentationFile>$(MSBuildProjectName).xml</DocumentationFile> | ||||||
|  | 	</PropertyGroup> | ||||||
|  |  | ||||||
|  | 	 | ||||||
|  | 	<PropertyGroup> | ||||||
|  | 		<LangVersion>latest</LangVersion> | ||||||
|  | 	</PropertyGroup> | ||||||
|  |  | ||||||
|  |  | ||||||
|  | </Project> | ||||||
| @@ -0,0 +1,64 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DynamicApiController; | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.Authorization; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  |  | ||||||
|  | using System.ComponentModel; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 后台登录控制器 | ||||||
|  | /// </summary> | ||||||
|  | [ApiDescriptionSettings(CateGoryConst.ThingsGatewayAdmin, Order = 200)] | ||||||
|  | [Route("auth/b")] | ||||||
|  | [LoggingMonitor] | ||||||
|  | public class AuthController : IDynamicApiController | ||||||
|  | { | ||||||
|  |     private readonly IAuthService _authService; | ||||||
|  |     /// <summary> | ||||||
|  |     /// <inheritdoc cref="AuthController"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="authService"></param> | ||||||
|  |     public AuthController(IAuthService authService) | ||||||
|  |     { | ||||||
|  |         _authService = authService; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 后台登录 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     [AllowAnonymous] | ||||||
|  |     [HttpPost("login")] | ||||||
|  |     [Description(EventSubscriberConst.Login)] | ||||||
|  |     public async Task<LoginOutput> LoginAsync(LoginInput input) | ||||||
|  |     { | ||||||
|  |         return await _authService.LoginAsync(input); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 后台登出 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     [HttpPost("logout")] | ||||||
|  |     [Description(EventSubscriberConst.Logout)] | ||||||
|  |     [Authorize] | ||||||
|  |     public async Task LogoutAsync() | ||||||
|  |     { | ||||||
|  |         await _authService.LogoutAsync(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,76 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DynamicApiController; | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 文件下载 | ||||||
|  | /// </summary> | ||||||
|  | [ApiDescriptionSettings(CateGoryConst.ThingsGatewayAdmin, Order = 200)] | ||||||
|  | [Route("file")] | ||||||
|  | [LoggingMonitor] | ||||||
|  | public class FileController : IDynamicApiController | ||||||
|  | { | ||||||
|  |     private readonly IFileService _fileService; | ||||||
|  |     private readonly IOperateLogService _operateLogService; | ||||||
|  |     private readonly IVisitLogService _visitLogService; | ||||||
|  |     /// <summary> | ||||||
|  |     /// <inheritdoc cref="FileController"/> | ||||||
|  |     /// </summary> | ||||||
|  |     public FileController( | ||||||
|  |         IFileService fileService, | ||||||
|  |         IOperateLogService operateLogService, | ||||||
|  |         IVisitLogService visitLogService | ||||||
|  |         ) | ||||||
|  |     { | ||||||
|  |         _fileService = fileService; | ||||||
|  |         _operateLogService = operateLogService; | ||||||
|  |         _visitLogService = visitLogService; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 下载操作日志 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     [HttpGet("operateLog")] | ||||||
|  |     public async Task<IActionResult> DownloadOperateLogAsync([FromQuery] OperateLogInput input) | ||||||
|  |     { | ||||||
|  |         var memoryStream = await _operateLogService.ExportFileAsync(input); | ||||||
|  |         memoryStream.Seek(0, SeekOrigin.Begin); | ||||||
|  |         var data = new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | ||||||
|  |         { | ||||||
|  |             FileDownloadName = $"operateLog{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx" | ||||||
|  |         }; | ||||||
|  |         return data; | ||||||
|  |     } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 下载访问日志 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     [HttpGet("visitLog")] | ||||||
|  |     public async Task<IActionResult> DownloadVisitLogAsync([FromQuery] VisitLogInput input) | ||||||
|  |     { | ||||||
|  |         var memoryStream = await _visitLogService.ExportFileAsync(input); | ||||||
|  |         memoryStream.Seek(0, SeekOrigin.Begin); | ||||||
|  |         var data = new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | ||||||
|  |         { | ||||||
|  |             FileDownloadName = $"operateLog{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx" | ||||||
|  |         }; | ||||||
|  |         return data; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,66 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DynamicApiController; | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.Authorization; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  |  | ||||||
|  | using System.ComponentModel; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// OpenApi登录控制器 | ||||||
|  |     /// </summary> | ||||||
|  |     [ApiDescriptionSettings(CateGoryConst.ThingsGatewayOpenApi, Order = 200)] | ||||||
|  |     [Route("auth/openapi")] | ||||||
|  |     [LoggingMonitor] | ||||||
|  |     [Description("OpenApi登录")] | ||||||
|  |     [Authorize(AuthenticationSchemes = "Bearer")] | ||||||
|  |     public class OpenApiAuthController : IDynamicApiController | ||||||
|  |     { | ||||||
|  |         private readonly IOpenApiAuthService _authService; | ||||||
|  |         /// <summary> | ||||||
|  |         /// <inheritdoc cref="OpenApiAuthController"/> | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="authService"></param> | ||||||
|  |         public OpenApiAuthController(IOpenApiAuthService authService) | ||||||
|  |         { | ||||||
|  |             _authService = authService; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// OpenApi登录 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="input"></param> | ||||||
|  |         /// <returns></returns> | ||||||
|  |         [AllowAnonymous] | ||||||
|  |         [HttpPost("login")] | ||||||
|  |         [Description(EventSubscriberConst.LoginOpenApi)] | ||||||
|  |         public async Task<LoginOpenApiOutput> LoginOpenApiAsync(LoginOpenApiInput input) | ||||||
|  |         { | ||||||
|  |             return await _authService.LoginOpenApiAsync(input); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 登出 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns></returns> | ||||||
|  |         [HttpPost("logout")] | ||||||
|  |         [Description(EventSubscriberConst.LogoutOpenApi)] | ||||||
|  |         public async Task LogoutAsync() | ||||||
|  |         { | ||||||
|  |             await _authService.LogoutAsync(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,71 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  | using Furion.DynamicApiController; | ||||||
|  | using Furion.SpecificationDocument; | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.Authorization; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Swagger登录授权服务 | ||||||
|  |     /// </summary> | ||||||
|  |     [ApiDescriptionSettings(CateGoryConst.ThingsGatewayAdmin, Order = 200)] | ||||||
|  |     [Route("Swagger")] | ||||||
|  |     public class SwaggerController : IDynamicApiController, IScoped | ||||||
|  |     { | ||||||
|  |         private readonly ConfigService _configService; | ||||||
|  |         /// <summary> | ||||||
|  |         /// <inheritdoc cref="SwaggerController"/> | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="sysConfigService"></param> | ||||||
|  |         public SwaggerController(ConfigService sysConfigService) | ||||||
|  |         { | ||||||
|  |             _configService = sysConfigService; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Swagger登录检查 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns></returns> | ||||||
|  |         [HttpPost("CheckUrl")] | ||||||
|  |         [AllowAnonymous, NonUnify] | ||||||
|  |         public async Task<int> SwaggerCheckUrlAsync() | ||||||
|  |         { | ||||||
|  |             var enable = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SWAGGERLOGIN_OPEN)).ConfigValue.ToBoolean(); | ||||||
|  |             return enable ? 401 : 200; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Swagger登录 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="auth"></param> | ||||||
|  |         /// <returns></returns> | ||||||
|  |         [HttpPost("SubmitUrl")] | ||||||
|  |         [AllowAnonymous, NonUnify] | ||||||
|  |         public async Task<int> SwaggerSubmitUrlAsync([FromForm] SpecificationAuth auth) | ||||||
|  |         { | ||||||
|  |             var userName = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SWAGGER_NAME)).ConfigValue; | ||||||
|  |             var password = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SWAGGER_PASSWORD)).ConfigValue; | ||||||
|  |             if (auth.UserName == userName && auth.Password == password) | ||||||
|  |             { | ||||||
|  |                 return 200; | ||||||
|  |             } | ||||||
|  |             return 401; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								framework/ThingsGateway.Admin.ApiController/GlobalUsings.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								framework/ThingsGateway.Admin.ApiController/GlobalUsings.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | global using System; | ||||||
|  | global using System.IO; | ||||||
|  | global using System.Threading.Tasks; | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  | 	<PropertyGroup> | ||||||
|  | 		<OpenApiGenerateDocuments>true</OpenApiGenerateDocuments> | ||||||
|  | 	</PropertyGroup> | ||||||
|  |  | ||||||
|  | 	<ItemGroup> | ||||||
|  | 		<None Include="..\.editorconfig" Link=".editorconfig" /> | ||||||
|  | 	</ItemGroup> | ||||||
|  | 	 | ||||||
|  | 	<ItemGroup> | ||||||
|  | 	  <ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" /> | ||||||
|  | 	</ItemGroup> | ||||||
|  | 	 | ||||||
|  | </Project> | ||||||
| @@ -0,0 +1,102 @@ | |||||||
|  | <?xml version="1.0"?> | ||||||
|  | <doc> | ||||||
|  |     <assembly> | ||||||
|  |         <name>ThingsGateway.Admin.ApiController</name> | ||||||
|  |     </assembly> | ||||||
|  |     <members> | ||||||
|  |         <member name="T:ThingsGateway.Admin.Application.AuthController"> | ||||||
|  |             <summary> | ||||||
|  |             后台登录控制器 | ||||||
|  |             </summary> | ||||||
|  |         </member> | ||||||
|  |         <member name="M:ThingsGateway.Admin.Application.AuthController.#ctor(ThingsGateway.Admin.Application.IAuthService)"> | ||||||
|  |             <summary> | ||||||
|  |             <inheritdoc cref="T:ThingsGateway.Admin.Application.AuthController"/> | ||||||
|  |             </summary> | ||||||
|  |             <param name="authService"></param> | ||||||
|  |         </member> | ||||||
|  |         <member name="M:ThingsGateway.Admin.Application.AuthController.LoginAsync(ThingsGateway.Admin.Application.LoginInput)"> | ||||||
|  |             <summary> | ||||||
|  |             后台登录 | ||||||
|  |             </summary> | ||||||
|  |             <param name="input"></param> | ||||||
|  |             <returns></returns> | ||||||
|  |         </member> | ||||||
|  |         <member name="M:ThingsGateway.Admin.Application.AuthController.LogoutAsync"> | ||||||
|  |             <summary> | ||||||
|  |             后台登出 | ||||||
|  |             </summary> | ||||||
|  |             <returns></returns> | ||||||
|  |         </member> | ||||||
|  |         <member name="T:ThingsGateway.Admin.Application.FileController"> | ||||||
|  |             <summary> | ||||||
|  |             文件下载 | ||||||
|  |             </summary> | ||||||
|  |         </member> | ||||||
|  |         <member name="M:ThingsGateway.Admin.Application.FileController.#ctor(ThingsGateway.Admin.Application.IFileService,ThingsGateway.Admin.Application.IOperateLogService,ThingsGateway.Admin.Application.IVisitLogService)"> | ||||||
|  |             <summary> | ||||||
|  |             <inheritdoc cref="T:ThingsGateway.Admin.Application.FileController"/> | ||||||
|  |             </summary> | ||||||
|  |         </member> | ||||||
|  |         <member name="M:ThingsGateway.Admin.Application.FileController.DownloadOperateLogAsync(ThingsGateway.Admin.Application.OperateLogInput)"> | ||||||
|  |             <summary> | ||||||
|  |             下载操作日志 | ||||||
|  |             </summary> | ||||||
|  |             <returns></returns> | ||||||
|  |         </member> | ||||||
|  |         <member name="M:ThingsGateway.Admin.Application.FileController.DownloadVisitLogAsync(ThingsGateway.Admin.Application.VisitLogInput)"> | ||||||
|  |             <summary> | ||||||
|  |             下载访问日志 | ||||||
|  |             </summary> | ||||||
|  |             <returns></returns> | ||||||
|  |         </member> | ||||||
|  |         <member name="T:ThingsGateway.Admin.Application.OpenApiAuthController"> | ||||||
|  |             <summary> | ||||||
|  |             OpenApi登录控制器 | ||||||
|  |             </summary> | ||||||
|  |         </member> | ||||||
|  |         <member name="M:ThingsGateway.Admin.Application.OpenApiAuthController.#ctor(ThingsGateway.Admin.Application.IOpenApiAuthService)"> | ||||||
|  |             <summary> | ||||||
|  |             <inheritdoc cref="T:ThingsGateway.Admin.Application.OpenApiAuthController"/> | ||||||
|  |             </summary> | ||||||
|  |             <param name="authService"></param> | ||||||
|  |         </member> | ||||||
|  |         <member name="M:ThingsGateway.Admin.Application.OpenApiAuthController.LoginOpenApiAsync(ThingsGateway.Admin.Application.LoginOpenApiInput)"> | ||||||
|  |             <summary> | ||||||
|  |             OpenApi登录 | ||||||
|  |             </summary> | ||||||
|  |             <param name="input"></param> | ||||||
|  |             <returns></returns> | ||||||
|  |         </member> | ||||||
|  |         <member name="M:ThingsGateway.Admin.Application.OpenApiAuthController.LogoutAsync"> | ||||||
|  |             <summary> | ||||||
|  |             登出 | ||||||
|  |             </summary> | ||||||
|  |             <returns></returns> | ||||||
|  |         </member> | ||||||
|  |         <member name="T:ThingsGateway.Admin.Application.SwaggerController"> | ||||||
|  |             <summary> | ||||||
|  |             Swagger登录授权服务 | ||||||
|  |             </summary> | ||||||
|  |         </member> | ||||||
|  |         <member name="M:ThingsGateway.Admin.Application.SwaggerController.#ctor(ThingsGateway.Admin.Application.ConfigService)"> | ||||||
|  |             <summary> | ||||||
|  |             <inheritdoc cref="T:ThingsGateway.Admin.Application.SwaggerController"/> | ||||||
|  |             </summary> | ||||||
|  |             <param name="sysConfigService"></param> | ||||||
|  |         </member> | ||||||
|  |         <member name="M:ThingsGateway.Admin.Application.SwaggerController.SwaggerCheckUrlAsync"> | ||||||
|  |             <summary> | ||||||
|  |             Swagger登录检查 | ||||||
|  |             </summary> | ||||||
|  |             <returns></returns> | ||||||
|  |         </member> | ||||||
|  |         <member name="M:ThingsGateway.Admin.Application.SwaggerController.SwaggerSubmitUrlAsync(Furion.SpecificationDocument.SpecificationAuth)"> | ||||||
|  |             <summary> | ||||||
|  |             Swagger登录 | ||||||
|  |             </summary> | ||||||
|  |             <param name="auth"></param> | ||||||
|  |             <returns></returns> | ||||||
|  |         </member> | ||||||
|  |     </members> | ||||||
|  | </doc> | ||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 操作事件说明特性 | ||||||
|  | /// </summary> | ||||||
|  | [AttributeUsage(AttributeTargets.Method)] | ||||||
|  | public class OperDescAttribute : Attribute | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 操作记录标识 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="description"></param> | ||||||
|  |     /// <param name="catcategory"></param> | ||||||
|  |     public OperDescAttribute(string description, string catcategory = LogConst.LOG_OPERATE) | ||||||
|  |     { | ||||||
|  |         Description = description; | ||||||
|  |         Catcategory = catcategory; | ||||||
|  |     } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 分类 | ||||||
|  |     /// </summary> | ||||||
|  |     public string Catcategory { get; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 说明 | ||||||
|  |     /// </summary> | ||||||
|  |     public string Description { get; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 记录参数,默认位true | ||||||
|  |     /// </summary> | ||||||
|  |     public bool IsRecordPar { get; set; } = true; | ||||||
|  | } | ||||||
| @@ -0,0 +1,224 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion; | ||||||
|  | using Furion.Reflection; | ||||||
|  | using Furion.Reflection.Extensions; | ||||||
|  |  | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | using UAParser; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// AOP处理操作日志 | ||||||
|  | /// </summary> | ||||||
|  | public class OperDispatchProxy : AspectDispatchProxy, IDispatchProxy | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 服务提供器,可以用来解析服务,如:Services.GetService() | ||||||
|  |     /// </summary> | ||||||
|  |     public IServiceProvider Services { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 当前服务实例 | ||||||
|  |     /// </summary> | ||||||
|  |     public object Target { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 方法 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="method"></param> | ||||||
|  |     /// <param name="args"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     /// <exception cref="NotImplementedException"></exception> | ||||||
|  |     public override object Invoke(MethodInfo method, object[] args) | ||||||
|  |     { | ||||||
|  |         var desc = Target.GetCustomAttribute<OperDescAttribute>(method.ToString(), true); | ||||||
|  |         if (desc == null) | ||||||
|  |         { | ||||||
|  |             return Invoke(method, args); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             Exception exception = default; | ||||||
|  |             object result = default; | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 result = Invoke(method, args); | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 exception = ex; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             WriteOperLog(method, args, desc, result, exception); | ||||||
|  |  | ||||||
|  |             if (exception != null) | ||||||
|  |             { | ||||||
|  |                 throw exception; | ||||||
|  |             } | ||||||
|  |             return result;//返回结果 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         object Invoke(MethodInfo method, object[] args) | ||||||
|  |         { | ||||||
|  |             //如果不带返回值 | ||||||
|  |             if (method.ReturnType == typeof(void)) | ||||||
|  |             { | ||||||
|  |                 return method.Invoke(Target, args);//直接返回 | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 var result = method.Invoke(Target, args); | ||||||
|  |                 return result;//返回结果 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 异步无返回值 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="method"></param> | ||||||
|  |     /// <param name="args"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     /// <exception cref="NotImplementedException"></exception> | ||||||
|  |     public override async Task InvokeAsync(MethodInfo method, object[] args) | ||||||
|  |     { | ||||||
|  |         var desc = method.GetActualCustomAttribute<OperDescAttribute>(Target); | ||||||
|  |         if (desc == null) | ||||||
|  |         { | ||||||
|  |             var task = method.Invoke(Target, args) as Task; | ||||||
|  |             await task; | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             Exception exception = default; | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 var task = method.Invoke(Target, args) as Task; | ||||||
|  |                 await task; | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 exception = ex; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             WriteOperLog(method, args, desc, null, exception); | ||||||
|  |  | ||||||
|  |             if (exception != null) | ||||||
|  |             { | ||||||
|  |                 throw exception; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 异步带返回值 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T"></typeparam> | ||||||
|  |     /// <param name="method"></param> | ||||||
|  |     /// <param name="args"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     /// <exception cref="NotImplementedException"></exception> | ||||||
|  |     public override async Task<T> InvokeAsyncT<T>(MethodInfo method, object[] args) | ||||||
|  |     { | ||||||
|  |         var desc = method.GetActualCustomAttribute<OperDescAttribute>(Target); | ||||||
|  |         if (desc == null) | ||||||
|  |         { | ||||||
|  |             var taskT = method.Invoke(Target, args) as Task<T>; | ||||||
|  |             var result = await taskT; | ||||||
|  |             return result;//返回结果 | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             T result = default; | ||||||
|  |             //写入操作日志 | ||||||
|  |             Exception exception = null; | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 var taskT = method.Invoke(Target, args) as Task<T>; | ||||||
|  |                 result = await taskT; | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 exception = ex; | ||||||
|  |             } | ||||||
|  |             WriteOperLog(method, args, desc, result, exception); | ||||||
|  |             if (exception != null) | ||||||
|  |             { | ||||||
|  |                 throw exception; | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 return result;//返回结果 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private static void WriteOperLog(MethodInfo method, object[] args, OperDescAttribute desc, object result, Exception exception) | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |         //写入操作日志 | ||||||
|  |         var str = App.HttpContext?.Request?.Headers?.UserAgent; | ||||||
|  |         ClientInfo clientInfo = null; | ||||||
|  |         if (str.HasValue) | ||||||
|  |         { | ||||||
|  |             clientInfo = StaticParser.Parser.Parse(str); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         StringBuilder stringBuilder = new(); | ||||||
|  |         if (desc.IsRecordPar) | ||||||
|  |         { | ||||||
|  |             var parameters = method.GetParameters(); | ||||||
|  |             var jsonParameters = parameters.Select((p, i) => $"\"{p.Name}\": {args[i].ToJsonString()}"); | ||||||
|  |             stringBuilder.Append("{"); | ||||||
|  |             stringBuilder.Append(string.Join(", ", jsonParameters)); | ||||||
|  |             stringBuilder.Append("}"); | ||||||
|  |         } | ||||||
|  |         var paramJson = stringBuilder.ToString(); | ||||||
|  |         var resultJson = desc.IsRecordPar ? result?.ToJsonString() : null; | ||||||
|  |         //操作日志表实体 | ||||||
|  |         var log = new SysOperateLog | ||||||
|  |         { | ||||||
|  |             Name = desc.Description, | ||||||
|  |             Category = desc.Catcategory, | ||||||
|  |             ExeStatus = LogConst.LOG_SUCCESS, | ||||||
|  |             OpIp = App.HttpContext?.Connection?.RemoteIpAddress?.MapToIPv4().ToString(), | ||||||
|  |             OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major, | ||||||
|  |             OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major, | ||||||
|  |             OpTime = SysDateTimeExtensions.CurrentDateTime, | ||||||
|  |             OpAccount = UserManager.UserAccount, | ||||||
|  |             ReqUrl = "", | ||||||
|  |             ReqMethod = LogConst.LOG_REQMETHOD, | ||||||
|  |             ResultJson = resultJson, | ||||||
|  |             ClassName = method.ReflectedType.Name, | ||||||
|  |             MethodName = method.Name, | ||||||
|  |             ParamJson = paramJson, | ||||||
|  |             VerificatId = UserManager.VerificatId.ToLong(), | ||||||
|  |         }; | ||||||
|  |         //如果异常不为空 | ||||||
|  |         if (exception != null) | ||||||
|  |         { | ||||||
|  |             log.ExeStatus = LogConst.LOG_FAIL;//操作状态为失败 | ||||||
|  |             log.ExeMessage = exception.Source + ":" + exception.Message + Environment.NewLine + exception.StackTrace; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         DbContext.Db.InsertableWithAttr(log).ExecuteCommand();//入库 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,38 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 权限操作常量 | ||||||
|  | /// </summary> | ||||||
|  | public class AdminConst | ||||||
|  | { | ||||||
|  |     #region 操作 | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 禁用操作 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string Disable = "禁用"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 启用操作 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string Enable = "启用"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户授权操作 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string GrantRole = "授权"; | ||||||
|  |  | ||||||
|  |     #endregion 操作 | ||||||
|  | } | ||||||
| @@ -0,0 +1,83 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Cache常量 | ||||||
|  | /// </summary> | ||||||
|  | public class CacheConst | ||||||
|  | { | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 登录验证码缓存Key | ||||||
|  |     /// </summary> | ||||||
|  |     public const string LOGIN_CAPTCHA = "LOGIN_CAPTCHA"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置缓存Key | ||||||
|  |     /// </summary> | ||||||
|  |     public const string SYS_CONFIGCATEGORY = "SYS_CONFIGCATEGORY"; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     #region OpenApi | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// OpenApi用户表缓存Key | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CACHE_OPENAPIUSER = "CACHE_OPENAPIUSER"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// OpenApi关系缓存Key | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CACHE_OPENAPIUSERACCOUNT = "CACHE_OPENAPIUSERACCOUNT"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// UserVerificat缓存Key | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CACHE_OPENAPIUSERVERIFICAT = "CACHE_OPENAPIUSERVERIFICAT"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// UserVerificat缓存Key | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CACHE_USERVERIFICAT = "CACHE_USERVERIFICAT"; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     #endregion OpenApi | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户表缓存Key | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CACHE_SYSUSER = "CACHE_SYSUSER"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户表缓存Key | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CAHCE_SYSUSERACCOUNT = "CAHCE_SYSUSERACCOUNT"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 关系表缓存Key | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CACHE_SYSRELATION = "CACHE_SYSRELATION"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 资源表缓存Key | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CACHE_SYSRESOURCE = "CACHE_SYSRESOURCE"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 角色表缓存Key | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CACHE_SYSROLE = "CACHE_SYSROLE"; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,61 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 其他分类常量 | ||||||
|  | /// </summary> | ||||||
|  | public static class CateGoryConst | ||||||
|  | { | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// ThingsGateway.Admin | ||||||
|  |     /// </summary> | ||||||
|  |     public const string ThingsGatewayAdmin = "ThingsGateway.Admin"; | ||||||
|  |     /// <summary> | ||||||
|  |     /// ThingsGateway.OpenApi | ||||||
|  |     /// </summary> | ||||||
|  |     public const string ThingsGatewayOpenApi = "ThingsGateway.OpenApi"; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     #region 关系表 | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户主页 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string Relation_SYS_USER_DEFAULTRAZOR = "Relation_SYS_USER_DEFAULTRAZOR"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户工作台数据 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string Relation_SYS_USER_WORKBENCH_DATA = "SYS_USER_WORKBENCH_DATA"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 角色有哪些权限 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string Relation_SYS_ROLE_HAS_PERMISSION = "SYS_ROLE_HAS_PERMISSION"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 角色有哪些资源 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string Relation_SYS_ROLE_HAS_RESOURCE = "SYS_ROLE_HAS_RESOURCE"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户有哪些角色 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string Relation_SYS_USER_HAS_ROLE = "SYS_USER_HAS_ROLE"; | ||||||
|  |     #endregion 关系表 | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,92 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 配置常量 | ||||||
|  | /// </summary> | ||||||
|  | public static class ConfigConst | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 系统固定配置 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string SYS_CONFIGBASEDEFAULT = "SYS_CONFIGBASEDEFAULT"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 其他自定义配置 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string SYS_CONFIGOTHER = "SYS_CONFIGOTHER"; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     #region config | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 版权标识 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CONFIG_COPYRIGHT = "CONFIG_COPYRIGHT"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 版权跳转url | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CONFIG_COPYRIGHT_URL = "CONFIG_COPYRIGHT_URL"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 登录验证码开关 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CONFIG_CAPTCHA_OPEN = "CONFIG_CAPTCHA_OPEN"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 默认用户密码 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CONFIG_PASSWORD = "CONFIG_PASSWORD"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 登录界面的介绍文本 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CONFIG_REMARK = "CONFIG_REMARK"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 单用户登录开关 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CONFIG_SINGLE_OPEN = "CONFIG_SINGLE_OPEN"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// swagger用户 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CONFIG_SWAGGER_NAME = "CONFIG_SWAGGER_NAME"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// swagger密码 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CONFIG_SWAGGER_PASSWORD = "CONFIG_SWAGGER_PASSWORD"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 系统标题 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CONFIG_TITLE = "CONFIG_TITLE"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 系统登录过期时间 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CONFIG_VERIFICAT_EXPIRES = "CONFIG_VERIFICAT_EXPIRES"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Swagger是否需要登录 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string CONFIG_SWAGGERLOGIN_OPEN = "CONFIG_SWAGGERLOGIN_OPEN"; | ||||||
|  |  | ||||||
|  |     #endregion | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,47 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 事件总线常量 | ||||||
|  | /// </summary> | ||||||
|  | public class EventSubscriberConst | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 清除用户缓存 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string ClearUserCache = "清除用户缓存"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 页面登录 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string Login = "登录"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// OpenApi登录 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string LoginOpenApi = "OpenApi登录"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 后台登出 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string Logout = "退出"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// OpenApi登出 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string LogoutOpenApi = "OpenApi退出"; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| //------------------------------------------------------------------------------ | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
| @@ -7,13 +8,14 @@ | |||||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Admin.Application; | namespace ThingsGateway.Admin.Application; | ||||||
| 
 | 
 | ||||||
| /// <summary> | /// <summary> | ||||||
| /// 通讯器常量 | /// 通讯器常量 | ||||||
| /// </summary> | /// </summary> | ||||||
| public static class HubConst | public class HubConst | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 系统HubUrl |     /// 系统HubUrl | ||||||
							
								
								
									
										70
									
								
								framework/ThingsGateway.Admin.Application/Const/LogConst.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								framework/ThingsGateway.Admin.Application/Const/LogConst.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 日志常量 | ||||||
|  | /// </summary> | ||||||
|  | public class LogConst | ||||||
|  | { | ||||||
|  |     #region 日志表 | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 登录 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string LOG_LOGIN = "LOGIN"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 登出 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string LOG_LOGOUT = "LOGOUT"; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 第三方登录 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string LOG_OPENAPILOGIN = "OPENAPILOGIN"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 第三方登出 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string LOG_OPENAPILOGOUT = "OPENAPILOGOUT"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 第三方操作来源 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string LOG_OPENAPIOPERATE = "OPENAPIOPERATE"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 操作分类 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string LOG_OPERATE = "OPERATE"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 内部操作来源 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string LOG_REQMETHOD = "BLAZORSERVER"; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 操作成功 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string LOG_SUCCESS = "SUCCESS"; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 操作失败 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string LOG_FAIL = "FAIL"; | ||||||
|  |     #endregion 日志表 | ||||||
|  | } | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 资源表常量 | ||||||
|  | /// </summary> | ||||||
|  | public class ResourceConst | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 系统内置编码 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string System = "system"; | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								framework/ThingsGateway.Admin.Application/Const/RoleConst.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								framework/ThingsGateway.Admin.Application/Const/RoleConst.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 角色常量 | ||||||
|  | /// </summary> | ||||||
|  | public class RoleConst | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 超级管理员 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string SuperAdmin = "superAdmin"; | ||||||
|  |  | ||||||
|  |     #region 关系表 | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 角色有哪些权限 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string Relation_SYS_ROLE_HAS_PERMISSION = "SYS_ROLE_HAS_PERMISSION"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 角色有哪些资源 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string Relation_SYS_ROLE_HAS_RESOURCE = "SYS_ROLE_HAS_RESOURCE"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户有哪些角色 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string Relation_SYS_USER_HAS_ROLE = "SYS_USER_HAS_ROLE"; | ||||||
|  |  | ||||||
|  |     #endregion 关系表 | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								framework/ThingsGateway.Admin.Application/GlobalUsings.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								framework/ThingsGateway.Admin.Application/GlobalUsings.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | global using System; | ||||||
|  | global using System.Collections.Generic; | ||||||
|  | global using System.IO; | ||||||
|  | global using System.Linq; | ||||||
|  | global using System.Threading; | ||||||
|  | global using System.Threading.Tasks; | ||||||
| @@ -0,0 +1,140 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion; | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.Authorization; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  |  | ||||||
|  | using System.ComponentModel; | ||||||
|  | using System.Globalization; | ||||||
|  | using System.Reflection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | using Yitter.IdGenerator; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 扩展方法 | ||||||
|  | /// </summary> | ||||||
|  | public class PermissionUtil | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取WebApi授权树 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public static List<OpenApiPermissionTreeSelector> OpenApiPermissionTreeSelector() | ||||||
|  |     { | ||||||
|  |         var cacheKey = $"{nameof(OpenApiPermissionTreeSelector)}-{CultureInfo.CurrentUICulture.Name}"; | ||||||
|  |         List<OpenApiPermissionTreeSelector> displayName = CacheStatic.Cache.GetOrCreate(cacheKey, entry => | ||||||
|  |         { | ||||||
|  |             List<OpenApiPermissionTreeSelector> openApiGroups = new(); | ||||||
|  |             var controllerTypes = App.EffectiveTypes | ||||||
|  |             .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(OpenApiPermissionAttribute), false)); | ||||||
|  |             foreach (var controller in controllerTypes) | ||||||
|  |             { | ||||||
|  |                 var GroupName = controller.GetCustomAttribute<ApiDescriptionSettingsAttribute>().GroupName; | ||||||
|  |                 if (GroupName == CateGoryConst.ThingsGatewayOpenApi) | ||||||
|  |                 { | ||||||
|  |                     var Description = controller.GetCustomAttribute<DescriptionAttribute>().Description; | ||||||
|  |                     var parid = YitIdHelper.NextId(); | ||||||
|  |                     OpenApiPermissionTreeSelector openApiGroup = new() { ApiName = Description, Id = parid, PermissionName = Description }; | ||||||
|  |                     var routeName = "/" + controller.GetCustomAttribute<RouteAttribute>().Template; | ||||||
|  |                     //获取所有方法 | ||||||
|  |                     var menthods = controller.GetMethods(); | ||||||
|  |                     //遍历方法 | ||||||
|  |                     foreach (var menthod in menthods) | ||||||
|  |                     { | ||||||
|  |                         //获取忽略数据权限特性 | ||||||
|  |                         var ignoreOpenApiPermission = menthod.GetCustomAttribute<IgnoreOpenApiPermissionAttribute>(); | ||||||
|  |                         if (ignoreOpenApiPermission == null)//如果是空的代表需要数据权限 | ||||||
|  |                         { | ||||||
|  |                             //获取接口描述 | ||||||
|  |                             var description = menthod.GetCustomAttribute<DescriptionAttribute>(); | ||||||
|  |                             if (description != null) | ||||||
|  |                             { | ||||||
|  |                                 //默认路由名称 | ||||||
|  |                                 var apiRoute = menthod.Name; | ||||||
|  |                                 //获取get特性 | ||||||
|  |                                 var requestGet = menthod.GetCustomAttribute<HttpGetAttribute>(); | ||||||
|  |                                 if (requestGet != null)//如果是get方法 | ||||||
|  |                                     apiRoute = requestGet.Template; | ||||||
|  |                                 else | ||||||
|  |                                 { | ||||||
|  |                                     //获取post特性 | ||||||
|  |                                     var requestPost = menthod.GetCustomAttribute<HttpPostAttribute>(); | ||||||
|  |                                     if (requestPost != null)//如果是post方法 | ||||||
|  |                                         apiRoute = requestPost.Template; | ||||||
|  |                                 } | ||||||
|  |                                 apiRoute = routeName + $"/{apiRoute}"; | ||||||
|  |                                 var apiName = description.Description;//如果描述不为空则接口名称用描述的名称 | ||||||
|  |  | ||||||
|  |                                 //合并 | ||||||
|  |                                 var permissionName = apiRoute + $"[{apiName}]"; | ||||||
|  |                                 //添加到权限列表 | ||||||
|  |                                 openApiGroup.Children.Add(new OpenApiPermissionTreeSelector | ||||||
|  |                                 { | ||||||
|  |                                     Id = YitIdHelper.NextId(), | ||||||
|  |                                     ParentId = parid, | ||||||
|  |                                     ApiName = apiName, | ||||||
|  |                                     ApiRoute = apiRoute, | ||||||
|  |                                     PermissionName = permissionName | ||||||
|  |                                 }); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     openApiGroups.Add(openApiGroup); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return openApiGroups; | ||||||
|  |         }, false); | ||||||
|  |         return displayName; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取全部页面权限内容 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="routers"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public static List<PermissionTreeSelector> PermissionTreeSelector(List<string> routers) | ||||||
|  |     { | ||||||
|  |         var cacheKey = $"{nameof(PermissionTreeSelector)}-{CultureInfo.CurrentUICulture.Name}-{routers.ToJsonString()}"; | ||||||
|  |         List<PermissionTreeSelector> displayName = CacheStatic.Cache.GetOrCreate(cacheKey, entry => | ||||||
|  |         { | ||||||
|  |             List<PermissionTreeSelector> permissions = new();//权限列表 | ||||||
|  |  | ||||||
|  |             // 获取所有需要数据权限的控制器 | ||||||
|  |             var controllerTypes = App.EffectiveTypes. | ||||||
|  |                 Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass | ||||||
|  |                 && u.IsDefined(typeof(AuthorizeAttribute), false) | ||||||
|  |                 && u.IsDefined(typeof(Microsoft.AspNetCore.Components.RouteAttribute), false)); | ||||||
|  |             foreach (var controller in controllerTypes) | ||||||
|  |             { | ||||||
|  |                 //获取数据权限特性 | ||||||
|  |                 var routeName = controller.GetCustomAttribute<Microsoft.AspNetCore.Components.RouteAttribute>()?.Template; | ||||||
|  |                 if (routeName == null) | ||||||
|  |                     continue; | ||||||
|  |                 if (routers.Contains(routeName)) | ||||||
|  |                 { | ||||||
|  |                     var apiRoute = $"{routeName}"; | ||||||
|  |                     permissions.Add(new() { ApiRoute = apiRoute }); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return permissions; | ||||||
|  |         }, false | ||||||
|  |         ); | ||||||
|  |         return displayName; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										124
									
								
								framework/ThingsGateway.Admin.Application/Helper/SeedDataUtil.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								framework/ThingsGateway.Admin.Application/Helper/SeedDataUtil.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 种子数据工具类 | ||||||
|  | /// </summary> | ||||||
|  | public class SeedDataUtil | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// json转化为种子列表 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T"></typeparam> | ||||||
|  |     /// <param name="jsonName"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public static List<T> GetSeedData<T>(string jsonName) | ||||||
|  |     { | ||||||
|  |         var seedData = new List<T>();//种子数据结果 | ||||||
|  |         var basePath = AppContext.BaseDirectory;//获取项目目录 | ||||||
|  |         var json = basePath.CombinePath("SeedData", "Json", jsonName);//获取文件路径 | ||||||
|  |         var dataString = ReadFile(json);//读取文件 | ||||||
|  |         if (!string.IsNullOrEmpty(dataString))//如果有内容 | ||||||
|  |         { | ||||||
|  |             //字段没有数据的替换成null | ||||||
|  |             dataString = dataString.Replace("\"\"", "null"); | ||||||
|  |             //将json字符串转为实体,这里extjson可以正常转换为字符串 | ||||||
|  |             var seedDataRecord = dataString.ToJsonWithT<SeedDataRecords<T>>(); | ||||||
|  |  | ||||||
|  |             //遍历seedDataRecord | ||||||
|  |             for (int i = 0; i < seedDataRecord.Records.Count; i++) | ||||||
|  |             { | ||||||
|  |                 #region 处理ExtJosn | ||||||
|  |  | ||||||
|  |                 //获取extjson属性 | ||||||
|  |                 var propertyExtJosn = typeof(T).GetProperty(nameof(PrimaryKeyEntity.ExtJson)); | ||||||
|  |                 if (propertyExtJosn != null) | ||||||
|  |                 { | ||||||
|  |                     //获取extjson的值 | ||||||
|  |                     var extJson = propertyExtJosn.GetValue(seedDataRecord.Records[i])?.ToString(); | ||||||
|  |                     // 如果extjson不为空并且包含NullableDictionary表示序列化失败了 | ||||||
|  |                     if (!string.IsNullOrEmpty(extJson) && extJson.Contains("NullableDictionary")) | ||||||
|  |                     { | ||||||
|  |                         //设置extjson为seedDataRecord对应的值 | ||||||
|  |                         extJson = propertyExtJosn.GetValue(seedDataRecord.Records[i])?.ToString(); | ||||||
|  |                         //seedDataRecord赋值seedDataRecord的extjson | ||||||
|  |                         propertyExtJosn.SetValue(seedDataRecord.Records[i], extJson); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 #endregion 处理ExtJosn | ||||||
|  |  | ||||||
|  |                 #region 处理ConfigValue | ||||||
|  |  | ||||||
|  |                 //获取extjson属性 | ||||||
|  |                 var propertyConfigValue = typeof(T).GetProperty(nameof(SysConfig.ConfigValue)); | ||||||
|  |                 if (propertyConfigValue != null) | ||||||
|  |                 { | ||||||
|  |                     //获取extjson的值 | ||||||
|  |                     var configValue = propertyConfigValue.GetValue(seedDataRecord.Records[i])?.ToString(); | ||||||
|  |                     // 如果extjson不为空并且包含NullableDictionary表示序列化失败了 | ||||||
|  |                     if (!string.IsNullOrEmpty(configValue) && configValue.Contains("NullableDictionary")) | ||||||
|  |                     { | ||||||
|  |                         //设置extjson为seedDataRecord对应的值 | ||||||
|  |                         configValue = propertyConfigValue.GetValue(seedDataRecord.Records[i])?.ToString(); | ||||||
|  |                         //seedDataRecord赋值seedDataRecord的extjson | ||||||
|  |                         propertyConfigValue.SetValue(seedDataRecord.Records[i], configValue); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 #endregion 处理ConfigValue | ||||||
|  |             } | ||||||
|  |             //种子数据赋值 | ||||||
|  |             seedData = seedDataRecord.Records; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return seedData; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 读取文件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="Path"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public static string ReadFile(string Path) | ||||||
|  |     { | ||||||
|  |         if (!File.Exists(Path)) | ||||||
|  |         { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); | ||||||
|  |         StreamReader streamReader = new(Path, Encoding.GetEncoding("utf-8")); | ||||||
|  |         string result = streamReader.ReadToEnd(); | ||||||
|  |         streamReader.Close(); | ||||||
|  |         streamReader.Dispose(); | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 种子数据格式实体类,遵循Navicat导出json格式 | ||||||
|  | /// </summary> | ||||||
|  | /// <typeparam name="T"></typeparam> | ||||||
|  |  | ||||||
|  | public class SeedDataRecords<T> | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 数据 | ||||||
|  |     /// </summary> | ||||||
|  |     public List<T> Records { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using UAParser; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 单例Parser | ||||||
|  | /// </summary> | ||||||
|  | public class StaticParser | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 单例 | ||||||
|  |     /// </summary> | ||||||
|  |     public static Parser Parser { get; } = Parser.GetDefault(); | ||||||
|  | } | ||||||
| @@ -0,0 +1,69 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion; | ||||||
|  | using Furion.Schedule; | ||||||
|  |  | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <inheritdoc cref="IJobPersistence"/> | ||||||
|  | public class JobPersistence : IJobPersistence | ||||||
|  | { | ||||||
|  |     private readonly IServiceScope _serviceScope; | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public JobPersistence(IServiceProvider serviceProvider) | ||||||
|  |     { | ||||||
|  |         _serviceScope = serviceProvider.CreateScope(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 作业调度服务启动时 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public IEnumerable<SchedulerBuilder> Preload() | ||||||
|  |     { | ||||||
|  |         // 获取所有定义的作业 | ||||||
|  |         var allJobs = App.EffectiveTypes.ScanToBuilders().ToList(); | ||||||
|  |         return allJobs; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 作业计划初始化通知 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="builder"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public SchedulerBuilder OnLoading(SchedulerBuilder builder) | ||||||
|  |     { | ||||||
|  |         return builder; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public void Dispose() | ||||||
|  |     { | ||||||
|  |         _serviceScope?.Dispose(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public void OnChanged(PersistenceContext context) | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public void OnTriggerChanged(PersistenceTriggerContext context) | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								framework/ThingsGateway.Admin.Application/Job/LogJob.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								framework/ThingsGateway.Admin.Application/Job/LogJob.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.Schedule; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 清理日志作业任务 | ||||||
|  | /// </summary> | ||||||
|  | [JobDetail("job_log", Description = "清理访问/操作日志", GroupName = "default", Concurrent = false)] | ||||||
|  | [Daily(TriggerId = "trigger_log", Description = "清理访问/操作日志", RunOnStart = true)] | ||||||
|  | public class LogJob : IJob | ||||||
|  | { | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) | ||||||
|  |     { | ||||||
|  |         var db = DbContext.Db.CopyNew(); | ||||||
|  |         var daysAgo = 30; // 删除30天以前 | ||||||
|  |         await db.Deleteable<SysVisitLog>().Where(u => u.CreateTime < SysDateTimeExtensions.CurrentDateTime.AddDays(-daysAgo)).ExecuteCommandAsync(); // 删除访问日志 | ||||||
|  |         await db.Deleteable<SysOperateLog>().Where(u => u.CreateTime < SysDateTimeExtensions.CurrentDateTime.AddDays(-daysAgo)).ExecuteCommandAsync(); // 删除操作日志 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,51 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 登录事件参数 | ||||||
|  | /// </summary> | ||||||
|  | public class LoginOpenApiEvent | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 时间 | ||||||
|  |     /// </summary> | ||||||
|  |     public DateTime DateTime = SysDateTimeExtensions.CurrentDateTime; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 登录设备 | ||||||
|  |     /// </summary> | ||||||
|  |     public AuthDeviceTypeEnum Device { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 过期时间(分) | ||||||
|  |     /// </summary> | ||||||
|  |     public int Expire { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Ip地址 | ||||||
|  |     /// </summary> | ||||||
|  |     public string Ip { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户信息 | ||||||
|  |     /// </summary> | ||||||
|  |     public OpenApiUser OpenApiUser { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 验证Id | ||||||
|  |     /// </summary> | ||||||
|  |     public long VerificatId { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,54 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  | using Furion.EventBus; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 认证模块事件总线 | ||||||
|  | /// </summary> | ||||||
|  | public class OpenApiAuthEventSubscriber : IEventSubscriber, ISingleton | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 登录事件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="context"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     [EventSubscribe(EventSubscriberConst.LoginOpenApi)] | ||||||
|  |     public async Task LoginOpenApi(EventHandlerExecutingContext context) | ||||||
|  |     { | ||||||
|  |         LoginOpenApiEvent loginEvent = (LoginOpenApiEvent)context.Source.Payload;//获取参数 | ||||||
|  |         OpenApiUser openApiUser = loginEvent.OpenApiUser; | ||||||
|  |         var db = DbContext.Db.CopyNew(); | ||||||
|  |         #region 重新赋值属性,设置本次登录信息为最新的信息 | ||||||
|  |  | ||||||
|  |         db.Tracking(openApiUser);//创建跟踪,只更新修改字段 | ||||||
|  |         openApiUser.LastLoginDevice = openApiUser.LatestLoginDevice; | ||||||
|  |         openApiUser.LastLoginIp = openApiUser.LatestLoginIp; | ||||||
|  |         openApiUser.LastLoginTime = openApiUser.LatestLoginTime; | ||||||
|  |         openApiUser.LatestLoginDevice = loginEvent.Device.ToString(); | ||||||
|  |         openApiUser.LatestLoginIp = loginEvent.Ip; | ||||||
|  |         openApiUser.LatestLoginTime = loginEvent.DateTime; | ||||||
|  |  | ||||||
|  |         #endregion 重新赋值属性,设置本次登录信息为最新的信息 | ||||||
|  |  | ||||||
|  |         //更新用户信息 | ||||||
|  |         if (await db.UpdateableWithAttr(openApiUser).ExecuteCommandAsync() > 0) | ||||||
|  |         { | ||||||
|  |             CacheStatic.Cache.Set(CacheConst.CACHE_OPENAPIUSER + openApiUser.Id, openApiUser, false); //更新Cache信息 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,33 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 登录输入参数 | ||||||
|  | /// </summary> | ||||||
|  | public class LoginOpenApiInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 账号 | ||||||
|  |     ///</summary> | ||||||
|  |     [Required(ErrorMessage = "账号不能为空"), MinLength(3, ErrorMessage = "账号不能少于4个字符")] | ||||||
|  |     public string Account { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 密码 | ||||||
|  |     ///</summary> | ||||||
|  |     [Required(ErrorMessage = "密码不能为空"), MinLength(3, ErrorMessage = "密码不能少于3个字符")] | ||||||
|  |     public string Password { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 登录返回参数 | ||||||
|  | /// </summary> | ||||||
|  | public class LoginOpenApiOutput : BaseLoginOutput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// TOKEN | ||||||
|  |     /// </summary> | ||||||
|  |     public string Token { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,33 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 登录服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface IOpenApiAuthService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 登录 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">登录参数</param> | ||||||
|  |     /// <returns>Token信息</returns> | ||||||
|  |     Task<LoginOpenApiOutput> LoginOpenApiAsync(LoginOpenApiInput input); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 登出 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task LogoutAsync(); | ||||||
|  | } | ||||||
| @@ -0,0 +1,173 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion; | ||||||
|  | using Furion.DataEncryption; | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  | using Furion.EventBus; | ||||||
|  | using Furion.FriendlyException; | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.Authentication; | ||||||
|  | using Microsoft.AspNetCore.Http; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | using Yitter.IdGenerator; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <inheritdoc cref="IOpenApiAuthService"/> | ||||||
|  | public class OpenApiAuthService : IOpenApiAuthService, ITransient | ||||||
|  | { | ||||||
|  |     private readonly IConfigService _configService; | ||||||
|  |     private readonly IEventPublisher _eventPublisher; | ||||||
|  |     private readonly IOpenApiUserService _openApiUserService; | ||||||
|  |     private readonly IVerificatService _verificatService; | ||||||
|  |  | ||||||
|  |     /// <inheritdoc cref="IOpenApiAuthService"/> | ||||||
|  |     public OpenApiAuthService( | ||||||
|  |                        IEventPublisher eventPublisher, | ||||||
|  |                        IOpenApiUserService openApiUserService, | ||||||
|  |                        IVerificatService verificatService, | ||||||
|  |                        IConfigService configService) | ||||||
|  |     { | ||||||
|  |         _verificatService = verificatService; | ||||||
|  |         _eventPublisher = eventPublisher; | ||||||
|  |         _openApiUserService = openApiUserService; | ||||||
|  |         _configService = configService; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<LoginOpenApiOutput> LoginOpenApiAsync(LoginOpenApiInput input) | ||||||
|  |     { | ||||||
|  |         var password = input.Password; | ||||||
|  |         var userInfo = await _openApiUserService.GetUserByAccountAsync(input.Account);//获取用户信息 | ||||||
|  |         if (userInfo == null) throw Oops.Bah("用户不存在");//用户不存在 | ||||||
|  |         if (userInfo.Password != password) throw Oops.Bah("账号密码错误");//账号密码错误 | ||||||
|  |         return await PrivateLoginOpenApiAsync(userInfo); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task LogoutAsync() | ||||||
|  |     { | ||||||
|  |         //获取用户信息 | ||||||
|  |         var userinfo = await _openApiUserService.GetUserByAccountAsync(UserManager.UserAccount); | ||||||
|  |         if (userinfo != null) | ||||||
|  |         { | ||||||
|  |             LoginOpenApiEvent loginEvent = new() | ||||||
|  |             { | ||||||
|  |                 Ip = App.HttpContext.GetRemoteIpAddressToIPv4(), | ||||||
|  |                 OpenApiUser = userinfo, | ||||||
|  |                 VerificatId = UserManager.VerificatId.ToLong(), | ||||||
|  |             }; | ||||||
|  |             await RemoveVerificatFromCacheAsync(loginEvent); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private async Task<List<VerificatInfo>> GetVerificatInfos(long userId) | ||||||
|  |     { | ||||||
|  |         List<VerificatInfo> verificatInfos = await _verificatService.GetOpenApiVerificatIdAsync(userId); | ||||||
|  |         return verificatInfos; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private async Task<LoginOpenApiOutput> PrivateLoginOpenApiAsync(OpenApiUser openApiUser) | ||||||
|  |     { | ||||||
|  |         if (openApiUser.UserEnable == false) throw Oops.Bah("账号已停用");//账号冻结 | ||||||
|  |         var sessionid = YitIdHelper.NextId(); | ||||||
|  |         var expire = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_VERIFICAT_EXPIRES)).ConfigValue.ToInt(); | ||||||
|  |         //生成Token | ||||||
|  |         var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object> | ||||||
|  |     { | ||||||
|  |         {ClaimConst.UserId, openApiUser.Id}, | ||||||
|  |         {ClaimConst.Account, openApiUser.Account}, | ||||||
|  |         { ClaimConst.VerificatId, sessionid.ToString()}, | ||||||
|  |         { ClaimConst.IsOpenApi, true}, | ||||||
|  |       }, expire); | ||||||
|  |         // 生成刷新Token令牌 | ||||||
|  |         var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, expire * 2); | ||||||
|  |         // 设置Swagger自动登录 | ||||||
|  |         App.HttpContext.SigninToSwagger(accessToken); | ||||||
|  |         // 设置响应报文头 | ||||||
|  |         App.HttpContext.SetTokensOfResponseHeaders(accessToken, refreshToken); | ||||||
|  |         //登录事件参数 | ||||||
|  |         var logingEvent = new LoginOpenApiEvent | ||||||
|  |         { | ||||||
|  |             Ip = App.HttpContext.GetRemoteIpAddressToIPv4(), | ||||||
|  |             Device = AuthDeviceTypeEnum.Api, | ||||||
|  |             Expire = expire, | ||||||
|  |             OpenApiUser = openApiUser, | ||||||
|  |             VerificatId = sessionid | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         await WriteVerificatToCacheAsync(logingEvent);//写入verificat到cache | ||||||
|  |         await _eventPublisher.PublishAsync(EventSubscriberConst.LoginOpenApi, logingEvent); //发布登录事件总线 | ||||||
|  |                                                                                             //返回结果 | ||||||
|  |         return new LoginOpenApiOutput { VerificatId = sessionid, Token = accessToken, Account = openApiUser.Account }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private async Task RemoveVerificatFromCacheAsync(LoginOpenApiEvent loginEvent) | ||||||
|  |     { | ||||||
|  |         //获取verificat列表 | ||||||
|  |         var verificatInfos = await GetVerificatInfos(loginEvent.OpenApiUser.Id); | ||||||
|  |         if (verificatInfos != null) | ||||||
|  |         { | ||||||
|  |             //获取当前用户的verificat | ||||||
|  |             var verificat = verificatInfos.Where(it => it.Id == loginEvent.VerificatId).FirstOrDefault(); | ||||||
|  |             if (verificat != null) | ||||||
|  |                 verificatInfos.Remove(verificat); | ||||||
|  |             //更新verificat列表 | ||||||
|  |             await _verificatService.SetOpenApiVerificatIdAsync(loginEvent.OpenApiUser.Id, verificatInfos); | ||||||
|  |         } | ||||||
|  |         await App.HttpContext?.SignOutAsync(); | ||||||
|  |         App.HttpContext?.SignoutToSwagger(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private async Task WriteVerificatToCacheAsync(LoginOpenApiEvent loginEvent) | ||||||
|  |     { | ||||||
|  |         //获取verificat列表 | ||||||
|  |         List<VerificatInfo> verificatInfos = await GetVerificatInfos(loginEvent.OpenApiUser.Id); | ||||||
|  |         var verificatTimeout = loginEvent.DateTime.AddMinutes(loginEvent.Expire); | ||||||
|  |         //生成verificat信息 | ||||||
|  |         var verificatInfo = new VerificatInfo | ||||||
|  |         { | ||||||
|  |             Device = loginEvent.Device.ToString(), | ||||||
|  |             Expire = loginEvent.Expire, | ||||||
|  |             VerificatTimeout = verificatTimeout, | ||||||
|  |             Id = loginEvent.VerificatId, | ||||||
|  |             UserId = loginEvent.OpenApiUser.Id, | ||||||
|  |         }; | ||||||
|  |         if (verificatInfos != null) | ||||||
|  |         { | ||||||
|  |             bool isSingle = false;//默认不开启单用户登录 | ||||||
|  |             var singleConfig = await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SINGLE_OPEN);//获取系统单用户登录选项 | ||||||
|  |             if (singleConfig != null) isSingle = singleConfig.ConfigValue.ToBoolean();//如果配置不为空则设置单用户登录选项为系统配置的值 | ||||||
|  |  | ||||||
|  |             //判断是否单用户登录 | ||||||
|  |             if (isSingle) | ||||||
|  |             { | ||||||
|  |                 verificatInfos = verificatInfos.ToList();//去掉当前登录类型的verificat | ||||||
|  |                 verificatInfos.Add(verificatInfo);//添加到列表 | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 verificatInfos.Add(verificatInfo); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             verificatInfos = new List<VerificatInfo> { verificatInfo };//直接就一个 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         //添加到verificat列表 | ||||||
|  |         await _verificatService.SetOpenApiVerificatIdAsync(loginEvent.OpenApiUser.Id, verificatInfos); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,47 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using System.ComponentModel; | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 会话分页查询 | ||||||
|  | /// </summary> | ||||||
|  | public class OpenApiSessionPageInput : BasePageInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 账号 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("账号")] | ||||||
|  |     public string Account { get; set; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 最新登录IP | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("最新登录IP")] | ||||||
|  |     public string LatestLoginIp { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 退出参数 | ||||||
|  | /// </summary> | ||||||
|  | public class OpenApiExitVerificatInput : BaseIdInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 验证ID列表 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "VerificatIds不能为空")] | ||||||
|  |     public List<long> VerificatIds { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,57 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using System.ComponentModel; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 会话输出 | ||||||
|  | /// </summary> | ||||||
|  | public class OpenApiSessionOutput : PrimaryKeyEntity | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 账号 | ||||||
|  |     ///</summary> | ||||||
|  |     [Description("账号")] | ||||||
|  |     [DataTable(Order = 1, IsShow = true, Sortable = true)] | ||||||
|  |     public virtual string Account { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 最新登录ip | ||||||
|  |     ///</summary> | ||||||
|  |     [Description("最新登录ip")] | ||||||
|  |     [DataTable(Order = 2, IsShow = true, Sortable = true)] | ||||||
|  |     public string LatestLoginIp { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 最新登录时间 | ||||||
|  |     ///</summary> | ||||||
|  |     [Description("最新登录时间")] | ||||||
|  |     [DataTable(Order = 3, IsShow = true, Sortable = true)] | ||||||
|  |     public DateTime? LatestLoginTime { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 令牌数量 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("令牌数量")] | ||||||
|  |     [DataTable(Order = 4, IsShow = true, Sortable = true)] | ||||||
|  |     public int VerificatCount { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 令牌信息集合 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("令牌列表")] | ||||||
|  |     public List<VerificatInfo> VerificatSignList { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,42 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 会话管理服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface IOpenApiSessionService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 强退会话 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">用户ID</param> | ||||||
|  |     Task ExitSessionAsync(long input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 强退cookie | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">cookie列表</param> | ||||||
|  |     Task ExitVerificatAsync(OpenApiExitVerificatInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// B端会话分页查询 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">查询参数</param> | ||||||
|  |     /// <returns>B端会话列表</returns> | ||||||
|  |     Task<SqlSugarPagedList<OpenApiSessionOutput>> PageAsync(OpenApiSessionPageInput input); | ||||||
|  | } | ||||||
| @@ -0,0 +1,108 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using SqlSugar; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// <inheritdoc cref="IOpenApiSessionService"/> | ||||||
|  | /// </summary> | ||||||
|  | [Injection(Proxy = typeof(OperDispatchProxy))] | ||||||
|  | public class OpenApiSessionService : DbRepository<OpenApiUser>, IOpenApiSessionService | ||||||
|  | { | ||||||
|  |  | ||||||
|  |     private readonly IVerificatService _verificatService; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <inheritdoc cref="IOpenApiSessionService"/> | ||||||
|  |     public OpenApiSessionService(IVerificatService verificatService) | ||||||
|  |     { | ||||||
|  |         _verificatService = verificatService; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("强退OPENAPI会话")] | ||||||
|  |     public async Task ExitSessionAsync(long input) | ||||||
|  |     { | ||||||
|  |         //从列表中删除 | ||||||
|  |         await _verificatService.SetOpenApiVerificatIdAsync(input, new()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("强退OPENAPI令牌")] | ||||||
|  |     public async Task ExitVerificatAsync(OpenApiExitVerificatInput input) | ||||||
|  |     { | ||||||
|  |         //获取该用户的verificat信息 | ||||||
|  |         List<VerificatInfo> verificatInfos = await _verificatService.GetOpenApiVerificatIdAsync(input.Id); | ||||||
|  |         //当前需要踢掉用户的verificat | ||||||
|  |         var setVerificats = verificatInfos.Where(it => !input.VerificatIds.Contains(it.Id)).ToList(); | ||||||
|  |         await _verificatService.SetOpenApiVerificatIdAsync(input.Id, setVerificats);//如果还有verificat则更新verificat | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取verificat剩余时间信息 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="verificatInfos">verificat列表</param> | ||||||
|  |     private void GetVerificatInfos(ref List<VerificatInfo> verificatInfos) | ||||||
|  |     { | ||||||
|  |         verificatInfos = verificatInfos.ToList(); | ||||||
|  |         verificatInfos.ForEach(it => | ||||||
|  |         { | ||||||
|  |             var now = SysDateTimeExtensions.CurrentDateTime; | ||||||
|  |             it.VerificatRemain = now.GetDiffTime(it.VerificatTimeout);//获取时间差 | ||||||
|  |             var verificatSecond = it.VerificatTimeout.AddMinutes(-it.Expire).ToLong();//颁发时间转为时间戳 | ||||||
|  |             var timeoutSecond = it.VerificatTimeout.ToLong();//过期时间转为时间戳 | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<SqlSugarPagedList<OpenApiSessionOutput>> PageAsync(OpenApiSessionPageInput input) | ||||||
|  |     { | ||||||
|  |         var query = Context.Queryable<OpenApiUser>() | ||||||
|  |             .WhereIF(!string.IsNullOrEmpty(input.Account), it => it.Account.Contains(input.Account))//根据账号查询 | ||||||
|  |             .WhereIF(!string.IsNullOrEmpty(input.LatestLoginIp), it => it.LatestLoginIp.Contains(input.LatestLoginIp))//根据IP查询 | ||||||
|  |             .OrderBy(it => it.LatestLoginTime, OrderByType.Desc) | ||||||
|  |             .Select<OpenApiSessionOutput>() | ||||||
|  |             .Mapper(async it => | ||||||
|  |             { | ||||||
|  |                 var verificatInfos = await _verificatService.GetVerificatIdAsync(it.Id); | ||||||
|  |                 if (verificatInfos != null) | ||||||
|  |                 { | ||||||
|  |                     GetVerificatInfos(ref verificatInfos);//获取剩余时间 | ||||||
|  |                     it.VerificatCount = verificatInfos.Count;//令牌数量 | ||||||
|  |                     it.VerificatSignList = verificatInfos;//令牌列表 | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     it.VerificatSignList = new(); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |             }); | ||||||
|  |         for (int i = 0; i < input.SortField.Count; i++) | ||||||
|  |         { | ||||||
|  |             query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页 | ||||||
|  |         pageInfo.Records = pageInfo.Records.OrderByDescending(it => it.VerificatCount); | ||||||
|  |         return pageInfo; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,150 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using SqlSugar; | ||||||
|  |  | ||||||
|  | using System.ComponentModel; | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Api授权资源树 | ||||||
|  | /// </summary> | ||||||
|  | public class OpenApiPermissionTreeSelector | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 接口描述 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("Api说明")] | ||||||
|  |     public string ApiName { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 路由名称 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("Api路径")] | ||||||
|  |     public string ApiRoute { get; set; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 子节点 | ||||||
|  |     /// </summary> | ||||||
|  |     public List<OpenApiPermissionTreeSelector> Children { get; set; } = new(); | ||||||
|  |     /// <summary> | ||||||
|  |     /// ID | ||||||
|  |     /// </summary> | ||||||
|  |     public long Id { get; set; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 父ID | ||||||
|  |     /// </summary> | ||||||
|  |     public long ParentId { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 权限名称 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("权限名称")] | ||||||
|  |     public string PermissionName { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 多个树转列表 | ||||||
|  |     /// </summary> | ||||||
|  |     public static List<OpenApiPermissionTreeSelector> TreeToList(IList<OpenApiPermissionTreeSelector> data) | ||||||
|  |     { | ||||||
|  |         List<OpenApiPermissionTreeSelector> list = new(); | ||||||
|  |         foreach (var item in data) | ||||||
|  |         { | ||||||
|  |             list.Add(item); | ||||||
|  |             if (item.Children != null && item.Children.Count > 0) | ||||||
|  |             { | ||||||
|  |                 list.AddRange(TreeToList(item.Children)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return list; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 添加用户参数 | ||||||
|  | /// </summary> | ||||||
|  | public class OpenApiUserAddInput : OpenApiUser | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 账号 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "账号不能为空"), MinLength(3, ErrorMessage = "账号不能少于4个字符")] | ||||||
|  |     public override string Account { get; set; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 密码 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "密码不能为空"), MinLength(2, ErrorMessage = "密码不能少于3个字符")] | ||||||
|  |     public override string Password { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     /// </summary> | ||||||
|  |     public override bool UserEnable { get; set; } = true; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 编辑用户参数 | ||||||
|  | /// </summary> | ||||||
|  | public class OpenApiUserEditInput : OpenApiUser | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 账号 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "账号不能为空"), MinLength(3, ErrorMessage = "账号不能少于4个字符")] | ||||||
|  |     public override string Account { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Id | ||||||
|  |     /// </summary> | ||||||
|  |     [MinValue(1, ErrorMessage = "Id不能为空")] | ||||||
|  |     public override long Id { get; set; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 密码 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "密码不能为空"), MinLength(2, ErrorMessage = "密码不能少于3个字符")] | ||||||
|  |     public override string Password { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 用户分页查询参数 | ||||||
|  | /// </summary> | ||||||
|  | public class OpenApiUserPageInput : BasePageInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 动态查询条件 | ||||||
|  |     /// </summary> | ||||||
|  |     public Expressionable<SysUser> Expression { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 用户授权参数 | ||||||
|  | /// </summary> | ||||||
|  | public class OpenApiUserGrantPermissionInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Id | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "Id不能为空")] | ||||||
|  |     public long? Id { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 授权权限信息 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "PermissionList不能为空")] | ||||||
|  |     public List<string> PermissionList { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,106 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 用户服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface IOpenApiUserService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加用户 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">添加参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task AddAsync(OpenApiUserAddInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 删除用户 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">Id列表</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task DeleteAsync(params long[] input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 从cache中删除用户信息 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="ids">用户ID列表</param> | ||||||
|  |     void DeleteUserFromCache(params long[] ids); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 禁用用户 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">用户Id</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task DisableUserAsync(long input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 编辑 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">编辑参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task EditAsync(OpenApiUserEditInput input); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 启用用户 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">用户Id</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task EnableUserAsync(long input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///根据用户账号获取用户ID | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="account">用户账号</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<long> GetIdByAccountAsync(string account); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据账号获取用户信息 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="account">用户名</param> | ||||||
|  |     /// <returns>用户信息</returns> | ||||||
|  |     Task<OpenApiUser> GetUserByAccountAsync(string account); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据ID获取用户信息 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="Id"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<OpenApiUser> GetUsertByIdAsync(long Id); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 给用户授权 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">授权参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task GrantRoleAsync(OpenApiUserGrantPermissionInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取用户拥有权限,返回的是服务方法名称 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">用户ID</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<string>> OwnPermissionsAsync(BaseIdInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户分页查询 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">查询参数</param> | ||||||
|  |     /// <returns>用户分页列表</returns> | ||||||
|  |     Task<SqlSugarPagedList<OpenApiUser>> PageAsync(OpenApiUserPageInput input); | ||||||
|  | } | ||||||
| @@ -0,0 +1,280 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DataEncryption; | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  | using Furion.FriendlyException; | ||||||
|  |  | ||||||
|  | using Mapster; | ||||||
|  |  | ||||||
|  | using SqlSugar; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// <inheritdoc cref="IOpenApiUserService"/> | ||||||
|  | /// </summary> | ||||||
|  | [Injection(Proxy = typeof(OperDispatchProxy))] | ||||||
|  | public class OpenApiUserService : DbRepository<OpenApiUser>, IOpenApiUserService | ||||||
|  | { | ||||||
|  |     private readonly IVerificatService _verificatService; | ||||||
|  |  | ||||||
|  |     /// <inheritdoc cref="IOpenApiUserService"/> | ||||||
|  |     public OpenApiUserService( | ||||||
|  |         IVerificatService verificatService | ||||||
|  |                       ) | ||||||
|  |     { | ||||||
|  |         _verificatService = verificatService; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("添加用户")] | ||||||
|  |     public async Task AddAsync(OpenApiUserAddInput input) | ||||||
|  |     { | ||||||
|  |         var account_Id = await GetIdByAccountAsync(input.Account); | ||||||
|  |         if (account_Id > 0) | ||||||
|  |             throw Oops.Bah($"存在重复的账号:{input.Account}"); | ||||||
|  |  | ||||||
|  |         var openApiUser = input.Adapt<OpenApiUser>();//实体转换 | ||||||
|  |         await InsertAsync(openApiUser);//添加数据 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("删除用户")] | ||||||
|  |     public async Task DeleteAsync(params long[] ids) | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |         //获取所有ID | ||||||
|  |         if (ids.Length > 0) | ||||||
|  |         { | ||||||
|  |             var result = await DeleteByIdsAsync(ids.Cast<object>().ToArray()); | ||||||
|  |             if (result) | ||||||
|  |             { | ||||||
|  |                 //从列表中删除 | ||||||
|  |                 foreach (var id in ids) | ||||||
|  |                 { | ||||||
|  |                     await _verificatService.SetOpenApiVerificatIdAsync(id, new()); | ||||||
|  |                 } | ||||||
|  |                 DeleteUserFromCache(ids); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public void DeleteUserFromCache(params long[] ids) | ||||||
|  |     { | ||||||
|  |         var userIds = ids.Select(it => it.ToString()).ToArray();//id转string列表 | ||||||
|  |         List<OpenApiUser> openApiUsers = new(); | ||||||
|  |         foreach (var item in userIds) | ||||||
|  |         { | ||||||
|  |             var user = CacheStatic.Cache.Get<OpenApiUser>(CacheConst.CACHE_OPENAPIUSER + item, false);//获取用户列表 | ||||||
|  |             openApiUsers.Add(user); | ||||||
|  |             CacheStatic.Cache.Remove(CacheConst.CACHE_OPENAPIUSER + item); | ||||||
|  |         } | ||||||
|  |         openApiUsers = openApiUsers.Where(it => it != null).ToList();//过滤掉不存在的 | ||||||
|  |         if (openApiUsers.Count > 0) | ||||||
|  |         { | ||||||
|  |             var accounts = openApiUsers.Select(it => it.Account).ToArray();//账号集合 | ||||||
|  |             foreach (var item in accounts) | ||||||
|  |             { | ||||||
|  |                 //删除账号 | ||||||
|  |                 CacheStatic.Cache.Remove(CacheConst.CACHE_OPENAPIUSERACCOUNT + item); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("禁用用户")] | ||||||
|  |     public async Task DisableUserAsync(long input) | ||||||
|  |     { | ||||||
|  |         var openApiUser = await GetUsertByIdAsync(input);//获取用户信息 | ||||||
|  |         if (openApiUser != null) | ||||||
|  |         { | ||||||
|  |             if (await UpdateAsync(it => new OpenApiUser { UserEnable = false }, it => it.Id == input)) | ||||||
|  |             { | ||||||
|  |                 await _verificatService.SetOpenApiVerificatIdAsync(input, new()); | ||||||
|  |                 DeleteUserFromCache(input);//从cache删除用户信息 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("编辑用户")] | ||||||
|  |     public async Task EditAsync(OpenApiUserEditInput input) | ||||||
|  |     { | ||||||
|  |         await CheckInputAsync(input);//检查参数 | ||||||
|  |         var exist = await GetUsertByIdAsync(input.Id);//获取用户信息 | ||||||
|  |         if (exist != null) | ||||||
|  |         { | ||||||
|  |             var openApiUser = input.Adapt<OpenApiUser>();//实体转换 | ||||||
|  |             openApiUser.Password = DESCEncryption.Encrypt(openApiUser.Password, DESCKeyConst.DESCKey); | ||||||
|  |             if (await Context.Updateable(openApiUser).IgnoreColumns(it => | ||||||
|  |             new | ||||||
|  |             { | ||||||
|  |                 //忽略更新字段 | ||||||
|  |                 it.LastLoginDevice, | ||||||
|  |                 it.LastLoginIp, | ||||||
|  |                 it.LastLoginTime, | ||||||
|  |                 it.LatestLoginDevice, | ||||||
|  |                 it.LatestLoginIp, | ||||||
|  |                 it.LatestLoginTime | ||||||
|  |             }).ExecuteCommandAsync() > 0)//修改数据 | ||||||
|  |                 DeleteUserFromCache(openApiUser.Id);//用户缓存到cache | ||||||
|  |         } | ||||||
|  |         //编辑操作可能会修改用户密码等信息,认证时需要实时获取用户并验证 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("启用用户")] | ||||||
|  |     public async Task EnableUserAsync(long input) | ||||||
|  |     { | ||||||
|  |         //设置状态为启用 | ||||||
|  |         if (await UpdateAsync(it => new OpenApiUser { UserEnable = true }, it => it.Id == input)) | ||||||
|  |             DeleteUserFromCache(input);//从cache删除用户信息 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<long> GetIdByAccountAsync(string account) | ||||||
|  |     { | ||||||
|  |         //先从Cache拿 | ||||||
|  |         var userId = CacheStatic.Cache.Get<long>(CacheConst.CACHE_OPENAPIUSERACCOUNT + account, false); | ||||||
|  |         if (userId == 0) | ||||||
|  |         { | ||||||
|  |             //单查获取用户账号对应ID | ||||||
|  |             userId = await GetFirstAsync(it => it.Account == account, it => it.Id); | ||||||
|  |             if (userId != 0) | ||||||
|  |             { | ||||||
|  |                 //插入Cache | ||||||
|  |                 CacheStatic.Cache.Set(CacheConst.CACHE_OPENAPIUSERACCOUNT + account, userId, false); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return userId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<OpenApiUser> GetUserByAccountAsync(string account) | ||||||
|  |     { | ||||||
|  |         var userId = await GetIdByAccountAsync(account);//获取用户ID | ||||||
|  |         if (userId > 0) | ||||||
|  |         { | ||||||
|  |             var openApiUser = await GetUsertByIdAsync(userId);//获取用户信息 | ||||||
|  |             if (openApiUser.Account == account)//这里做了比较用来限制大小写 | ||||||
|  |                 return openApiUser; | ||||||
|  |             else | ||||||
|  |                 return null; | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<OpenApiUser> GetUsertByIdAsync(long Id) | ||||||
|  |     { | ||||||
|  |         //先从Cache拿,需要获取新的对象,避免操作导致缓存中对象改变 | ||||||
|  |         var openApiUser = CacheStatic.Cache.Get<OpenApiUser>(CacheConst.CACHE_OPENAPIUSER + Id.ToString(), true); | ||||||
|  |         if (openApiUser == null) | ||||||
|  |         { | ||||||
|  |             openApiUser = await Context.Queryable<OpenApiUser>() | ||||||
|  |             .Where(u => u.Id == Id) | ||||||
|  |             .FirstAsync(); | ||||||
|  |             if (openApiUser != null) | ||||||
|  |             { | ||||||
|  |                 //插入Cache | ||||||
|  |                 CacheStatic.Cache.Set(CacheConst.CACHE_OPENAPIUSER + openApiUser.Id.ToString(), openApiUser, true); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return openApiUser; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     [OperDesc("用户授权")] | ||||||
|  |     public async Task GrantRoleAsync(OpenApiUserGrantPermissionInput input) | ||||||
|  |     { | ||||||
|  |         var openApiUser = await GetUsertByIdAsync(input.Id.Value);//获取用户信息 | ||||||
|  |         if (openApiUser != null) | ||||||
|  |         { | ||||||
|  |             openApiUser.PermissionCodeList = input.PermissionList; | ||||||
|  |             await CheckInputAsync(openApiUser); | ||||||
|  |             if (await Context.Updateable(openApiUser).IgnoreColumns(it => | ||||||
|  |            new | ||||||
|  |            { | ||||||
|  |                //忽略更新字段 | ||||||
|  |                it.Password, | ||||||
|  |                it.LastLoginDevice, | ||||||
|  |                it.LastLoginIp, | ||||||
|  |                it.LastLoginTime, | ||||||
|  |                it.LatestLoginDevice, | ||||||
|  |                it.LatestLoginIp, | ||||||
|  |                it.LatestLoginTime | ||||||
|  |            }).ExecuteCommandAsync() > 0)//修改数据 | ||||||
|  |                 DeleteUserFromCache(input.Id.Value);//从cache删除用户信息 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<string>> OwnPermissionsAsync(BaseIdInput input) | ||||||
|  |     { | ||||||
|  |         var openApiUser = await GetUsertByIdAsync(input.Id);//获取用户信息 | ||||||
|  |         return openApiUser.PermissionCodeList; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<SqlSugarPagedList<OpenApiUser>> PageAsync(OpenApiUserPageInput input) | ||||||
|  |     { | ||||||
|  |         var query = Context.Queryable<OpenApiUser>() | ||||||
|  |          .WhereIF(!string.IsNullOrEmpty(input.SearchKey), u => u.Account.Contains(input.SearchKey));//根据关键字查询 | ||||||
|  |         for (int i = 0; i < input.SortField.Count; i++) | ||||||
|  |         { | ||||||
|  |             query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}"); | ||||||
|  |         } | ||||||
|  |         query = query.OrderBy(it => it.SortCode);//排序 | ||||||
|  |         query = query.OrderBy(u => u.Id);//排序 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页 | ||||||
|  |         return pageInfo; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 检查输入参数 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="openApiUser"></param> | ||||||
|  |     private async Task CheckInputAsync(OpenApiUser openApiUser) | ||||||
|  |     { | ||||||
|  |         //判断账号重复,直接从cache拿 | ||||||
|  |         var account_Id = await GetIdByAccountAsync(openApiUser.Account); | ||||||
|  |         if (account_Id > 0 && account_Id != openApiUser.Id) | ||||||
|  |             throw Oops.Bah($"存在重复的账号:{openApiUser.Account}"); | ||||||
|  |         //如果手机号不是空 | ||||||
|  |         if (!string.IsNullOrEmpty(openApiUser.Phone)) | ||||||
|  |         { | ||||||
|  |             if (!openApiUser.Phone.MatchPhoneNumber())//验证手机格式 | ||||||
|  |                 throw Oops.Bah($"手机号码:{openApiUser.Phone} 格式错误"); | ||||||
|  |             openApiUser.Phone = DESCEncryption.Encrypt(openApiUser.Phone, DESCKeyConst.DESCKey); | ||||||
|  |         } | ||||||
|  |         //如果邮箱不是空 | ||||||
|  |         if (!string.IsNullOrEmpty(openApiUser.Email)) | ||||||
|  |         { | ||||||
|  |             var ismatch = openApiUser.Email.MatchEmail();//验证邮箱格式 | ||||||
|  |             if (!ismatch) | ||||||
|  |                 throw Oops.Bah($"邮箱:{openApiUser.Email} 格式错误"); | ||||||
|  |             if (await IsAnyAsync(it => it.Email == openApiUser.Email && it.Id != openApiUser.Id)) | ||||||
|  |                 throw Oops.Bah($"存在重复的邮箱:{openApiUser.Email}"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,104 @@ | |||||||
|  | { | ||||||
|  |   "RECORDS": [ | ||||||
|  |     { | ||||||
|  |       "Id": "22222222222222", | ||||||
|  |       "Category": "SYS_CONFIGBASEDEFAULT", | ||||||
|  |       "ConfigKey": "CONFIG_SWAGGER_NAME", | ||||||
|  |       "ConfigValue": "admin", | ||||||
|  |       "Remark": "swagger账号", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "22222222222223", | ||||||
|  |       "Category": "SYS_CONFIGBASEDEFAULT", | ||||||
|  |       "ConfigKey": "CONFIG_SWAGGER_PASSWORD", | ||||||
|  |       "ConfigValue": "123456", | ||||||
|  |       "Remark": "swagger密码", | ||||||
|  |       "SortCode": "2", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "22222222222224", | ||||||
|  |       "Category": "SYS_CONFIGBASEDEFAULT", | ||||||
|  |       "ConfigKey": "CONFIG_SWAGGERLOGIN_OPEN", | ||||||
|  |       "ConfigValue": "false", | ||||||
|  |       "Remark": "swagger开启登录", | ||||||
|  |       "SortCode": "3", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     { | ||||||
|  |       "Id": "22222222222226", | ||||||
|  |       "Category": "SYS_CONFIGBASEDEFAULT", | ||||||
|  |       "ConfigKey": "CONFIG_TITLE", | ||||||
|  |       "ConfigValue": "ThingsGateway", | ||||||
|  |       "Remark": "标题", | ||||||
|  |       "SortCode": "5", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "22222222222228", | ||||||
|  |       "Category": "SYS_CONFIGBASEDEFAULT", | ||||||
|  |       "ConfigKey": "CONFIG_COPYRIGHT", | ||||||
|  |       "ConfigValue": "ThingsGateway ©2023 Diego", | ||||||
|  |       "Remark": "系统版权", | ||||||
|  |       "SortCode": "6", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "22222222222229", | ||||||
|  |       "Category": "SYS_CONFIGBASEDEFAULT", | ||||||
|  |       "ConfigKey": "CONFIG_COPYRIGHT_URL", | ||||||
|  |       "ConfigValue": "https://gitee.com/diego2098/ThingsGateway", | ||||||
|  |       "Remark": "系统版权链接地址", | ||||||
|  |       "SortCode": "7", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "22222222222231", | ||||||
|  |       "Category": "SYS_CONFIGBASEDEFAULT", | ||||||
|  |       "ConfigKey": "CONFIG_PASSWORD", | ||||||
|  |       "ConfigValue": "111111", | ||||||
|  |       "Remark": "默认用户密码", | ||||||
|  |       "SortCode": "8", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "22222222222227", | ||||||
|  |       "Category": "SYS_CONFIGBASEDEFAULT", | ||||||
|  |       "ConfigKey": "CONFIG_VERIFICAT_EXPIRES", | ||||||
|  |       "ConfigValue": "14400", | ||||||
|  |       "Remark": "Verificat过期时间(分)", | ||||||
|  |       "SortCode": "9", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "22222222222232", | ||||||
|  |       "Category": "SYS_CONFIGBASEDEFAULT", | ||||||
|  |       "ConfigKey": "CONFIG_SINGLE_OPEN", | ||||||
|  |       "ConfigValue": "false", | ||||||
|  |       "Remark": "单用户登录开关", | ||||||
|  |       "SortCode": "10", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "22222222222230", | ||||||
|  |       "Category": "SYS_CONFIGBASEDEFAULT", | ||||||
|  |       "ConfigKey": "CONFIG_CAPTCHA_OPEN", | ||||||
|  |       "ConfigValue": "true", | ||||||
|  |       "Remark": "登录验证码开关", | ||||||
|  |       "SortCode": "11", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "22222222222225", | ||||||
|  |       "Category": "SYS_CONFIGBASEDEFAULT", | ||||||
|  |       "ConfigKey": "CONFIG_REMARK", | ||||||
|  |       "ConfigValue": "边缘采集网关", | ||||||
|  |       "Remark": "说明", | ||||||
|  |       "SortCode": "12", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | { | ||||||
|  |   "RECORDS": [ | ||||||
|  |     { | ||||||
|  |       "Id": 444657867911429, | ||||||
|  |       "Category": "SYS_USER_HAS_ROLE", | ||||||
|  |       "ObjectId": 212725263002001, | ||||||
|  |       "TargetId": "212725263001001", | ||||||
|  |       "ExtJson": null | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": 444657879060741, | ||||||
|  |       "Category": "SYS_USER_HAS_ROLE", | ||||||
|  |       "ObjectId": 201725263002001, | ||||||
|  |       "TargetId": "212725263001002", | ||||||
|  |       "ExtJson": null | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @@ -0,0 +1,564 @@ | |||||||
|  | { | ||||||
|  |   "RECORDS": [ | ||||||
|  |     { | ||||||
|  |       "Id": "100", | ||||||
|  |       "Title": "系统首页", | ||||||
|  |       "Icon": "mdi-home-account", | ||||||
|  |       "Name": "index", | ||||||
|  |       "Component": "/index", | ||||||
|  |       "Category": "SPA", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "0", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001", | ||||||
|  |       "Title": "权限管理", | ||||||
|  |       "Icon": "mdi-account-hard-hat", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "0", | ||||||
|  |       "SortCode": "4", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false", | ||||||
|  |       "UpdateTime": "2023-02-26 00:55:23.977", | ||||||
|  |       "UpdateUser": "superAdmin", | ||||||
|  |       "UpdateUserId": "212725263002001" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001001", | ||||||
|  |       "Title": "用户管理", | ||||||
|  |       "Icon": "mdi-account-edit", | ||||||
|  |       "Name": "sysUser", | ||||||
|  |       "Component": "/admin/user", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "100001", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001002", | ||||||
|  |       "Title": "角色管理", | ||||||
|  |       "Icon": "mdi-account-hard-hat", | ||||||
|  |       "Name": "sysRole", | ||||||
|  |       "Component": "/admin/role", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "100001", | ||||||
|  |       "SortCode": "2", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001003", | ||||||
|  |       "Title": "菜单管理", | ||||||
|  |       "Icon": "mdi-menu", | ||||||
|  |       "Name": "sysMenu", | ||||||
|  |       "Component": "/admin/menu", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "100001", | ||||||
|  |       "SortCode": "3", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002002", | ||||||
|  |       "Title": "访问日志", | ||||||
|  |       "Icon": "mdi-account-switch-outline", | ||||||
|  |       "Name": "sysVislog", | ||||||
|  |       "Component": "/admin/vislog", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "100002", | ||||||
|  |       "SortCode": "2", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002003", | ||||||
|  |       "Title": "操作日志", | ||||||
|  |       "Icon": "mdi-database-search-outline", | ||||||
|  |       "Name": "sysOplog", | ||||||
|  |       "Component": "/admin/oplog", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "100002", | ||||||
|  |       "SortCode": "3", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002010", | ||||||
|  |       "Title": "定时看板", | ||||||
|  |       "Icon": "mdi-database-cog-outline", | ||||||
|  |       "Name": "schedulePage", | ||||||
|  |       "Component": "/schedulePage", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "100002", | ||||||
|  |       "SortCode": "4", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002", | ||||||
|  |       "Title": "系统运维", | ||||||
|  |       "Icon": "mdi-cogs", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "0", | ||||||
|  |       "SortCode": "5", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false", | ||||||
|  |       "UpdateTime": "2023-02-26 00:55:33.503", | ||||||
|  |       "UpdateUser": "superAdmin", | ||||||
|  |       "UpdateUserId": "212725263002001" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002001", | ||||||
|  |       "Title": "系统配置", | ||||||
|  |       "Icon": "mdi-cog-transfer-outline", | ||||||
|  |       "Name": "sysConfig", | ||||||
|  |       "Component": "/admin/config", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "100002", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100003", | ||||||
|  |       "Title": "第三方授权", | ||||||
|  |       "Icon": "mdi-transit-transfer", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "0", | ||||||
|  |       "SortCode": "6", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false", | ||||||
|  |       "UpdateTime": "2023-02-26 00:55:29.094", | ||||||
|  |       "UpdateUser": "superAdmin", | ||||||
|  |       "UpdateUserId": "212725263002001" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100003001", | ||||||
|  |       "Title": "令牌列表", | ||||||
|  |       "Icon": "mdi-transit-transfer", | ||||||
|  |       "Name": "sysOpenApiSession", | ||||||
|  |       "Component": "/admin/openapisession", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "100003", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100003002", | ||||||
|  |       "Title": "授权用户", | ||||||
|  |       "Icon": "mdi-transit-transfer", | ||||||
|  |       "Name": "sysOpenApiUser", | ||||||
|  |       "Component": "/admin/openapiuser", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "100003", | ||||||
|  |       "SortCode": "2", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100003003", | ||||||
|  |       "Title": "接口文档", | ||||||
|  |       "Icon": "mdi-cog-transfer-outline", | ||||||
|  |       "Name": "swaggerUrl", | ||||||
|  |       "Component": "/api/index.html", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "100003", | ||||||
|  |       "SortCode": "3", | ||||||
|  |       "TargetType": "BLANK", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002004", | ||||||
|  |       "Title": "会话管理", | ||||||
|  |       "Icon": "mdi-transit-transfer", | ||||||
|  |       "Name": "session", | ||||||
|  |       "Component": "/admin/session", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "100002", | ||||||
|  |       "SortCode": "4", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001001001", | ||||||
|  |       "Title": "添加", | ||||||
|  |       "Name": "add", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysuseradd", | ||||||
|  |       "ParentId": "100001001", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001004", | ||||||
|  |       "Title": "单页管理", | ||||||
|  |       "Icon": "mdi-menu", | ||||||
|  |       "Name": "sysSpa", | ||||||
|  |       "Component": "/admin/spa", | ||||||
|  |       "Category": "MENU", | ||||||
|  |       "Code": "system", | ||||||
|  |       "ParentId": "100001", | ||||||
|  |       "SortCode": "4", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001001002", | ||||||
|  |       "Title": "编辑", | ||||||
|  |       "Name": "edit", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysuseredit", | ||||||
|  |       "ParentId": "100001001", | ||||||
|  |       "SortCode": "2", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001001003", | ||||||
|  |       "Title": "删除", | ||||||
|  |       "Name": "delete", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysuserdelete", | ||||||
|  |       "ParentId": "100001001", | ||||||
|  |       "SortCode": "3", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001002001", | ||||||
|  |       "Title": "添加", | ||||||
|  |       "Name": "add", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysroleadd", | ||||||
|  |       "ParentId": "100001002", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001002002", | ||||||
|  |       "Title": "编辑", | ||||||
|  |       "Name": "edit", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysroleedit", | ||||||
|  |       "ParentId": "100001002", | ||||||
|  |       "SortCode": "2", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001002003", | ||||||
|  |       "Title": "删除", | ||||||
|  |       "Name": "delete", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysroledelete", | ||||||
|  |       "ParentId": "100001002", | ||||||
|  |       "SortCode": "3", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001003001", | ||||||
|  |       "Title": "添加", | ||||||
|  |       "Name": "add", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysmenuadd", | ||||||
|  |       "ParentId": "100001003", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001003002", | ||||||
|  |       "Title": "编辑", | ||||||
|  |       "Name": "edit", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysmenuedit", | ||||||
|  |       "ParentId": "100001003", | ||||||
|  |       "SortCode": "2", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001003003", | ||||||
|  |       "Title": "删除", | ||||||
|  |       "Name": "delete", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysmenudelete", | ||||||
|  |       "ParentId": "100001003", | ||||||
|  |       "SortCode": "3", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001004001", | ||||||
|  |       "Title": "添加", | ||||||
|  |       "Name": "add", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysspaadd", | ||||||
|  |       "ParentId": "100001004", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001004002", | ||||||
|  |       "Title": "编辑", | ||||||
|  |       "Name": "edit", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysspaedit", | ||||||
|  |       "ParentId": "100001004", | ||||||
|  |       "SortCode": "2", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001004003", | ||||||
|  |       "Title": "删除", | ||||||
|  |       "Name": "delete", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysspadelete", | ||||||
|  |       "ParentId": "100001004", | ||||||
|  |       "SortCode": "3", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002001001", | ||||||
|  |       "Title": "添加", | ||||||
|  |       "Name": "add", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysconfigadd", | ||||||
|  |       "ParentId": "100002001", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002001002", | ||||||
|  |       "Title": "编辑", | ||||||
|  |       "Name": "edit", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysconfigedit", | ||||||
|  |       "ParentId": "100002001", | ||||||
|  |       "SortCode": "2", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002001003", | ||||||
|  |       "Title": "删除", | ||||||
|  |       "Name": "delete", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysconfigdelete", | ||||||
|  |       "ParentId": "100002001", | ||||||
|  |       "SortCode": "3", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100003002001", | ||||||
|  |       "Title": "添加", | ||||||
|  |       "Name": "add", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "openapiuseradd", | ||||||
|  |       "ParentId": "100003002", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100003002002", | ||||||
|  |       "Title": "编辑", | ||||||
|  |       "Name": "edit", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "openapiuseredit", | ||||||
|  |       "ParentId": "100003002", | ||||||
|  |       "SortCode": "2", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100003002003", | ||||||
|  |       "Title": "删除", | ||||||
|  |       "Name": "delete", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "openapiuserdelete", | ||||||
|  |       "ParentId": "100003002", | ||||||
|  |       "SortCode": "3", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001001004", | ||||||
|  |       "Title": "重置密码", | ||||||
|  |       "Name": "resetpassword", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysuserresetpassword", | ||||||
|  |       "ParentId": "100001001", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001001005", | ||||||
|  |       "Title": "角色授权", | ||||||
|  |       "Name": "perrole", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysuserperrole", | ||||||
|  |       "ParentId": "100001001", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001002004", | ||||||
|  |       "Title": "资源授权", | ||||||
|  |       "Name": "perresuorce", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysroleperresuorce", | ||||||
|  |       "ParentId": "100001002", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100001002005", | ||||||
|  |       "Title": "用户授权", | ||||||
|  |       "Name": "peruser", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysroleperuser", | ||||||
|  |       "ParentId": "100001002", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002004004", | ||||||
|  |       "Title": "会话强退", | ||||||
|  |       "Name": "exit", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "syssessionexit", | ||||||
|  |       "ParentId": "100002004", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002004005", | ||||||
|  |       "Title": "令牌删除", | ||||||
|  |       "Name": "verificatdelete", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysverificatdelete", | ||||||
|  |       "ParentId": "100002004", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100003001004", | ||||||
|  |       "Title": "会话强退", | ||||||
|  |       "Name": "exit", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "openapisessionexit", | ||||||
|  |       "ParentId": "100003001", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100003001005", | ||||||
|  |       "Title": "令牌删除", | ||||||
|  |       "Name": "verificatdelete", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "openapiverificatdelete", | ||||||
|  |       "ParentId": "100003001", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002002003", | ||||||
|  |       "Title": "清空", | ||||||
|  |       "Name": "clear", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysoplogclear", | ||||||
|  |       "ParentId": "100002002", | ||||||
|  |       "SortCode": "3", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100002003003", | ||||||
|  |       "Title": "清空", | ||||||
|  |       "Name": "clear", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "sysvislogclear", | ||||||
|  |       "ParentId": "100002003", | ||||||
|  |       "SortCode": "3", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100003002004", | ||||||
|  |       "Title": "修改密码", | ||||||
|  |       "Name": "editpassword", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "openapiusereditpassword", | ||||||
|  |       "ParentId": "100003002", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "100003002005", | ||||||
|  |       "Title": "授权Api", | ||||||
|  |       "Name": "per", | ||||||
|  |       "Category": "BUTTON", | ||||||
|  |       "Code": "openapiuserper", | ||||||
|  |       "ParentId": "100003002", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "TargetType": "None", | ||||||
|  |       "IsDelete": "false" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "391545543004421", | ||||||
|  |       "Title": "个人中心", | ||||||
|  |       "Icon": "mdi-home-account", | ||||||
|  |       "Component": "/usercenter", | ||||||
|  |       "Category": "SPA", | ||||||
|  |       "Code": "391545542885637", | ||||||
|  |       "ParentId": "0", | ||||||
|  |       "SortCode": "2", | ||||||
|  |       "TargetType": "SELF", | ||||||
|  |       "CreateTime": "2023-03-02 19:42:55.6049703", | ||||||
|  |       "CreateUser": "superAdmin", | ||||||
|  |       "CreateUserId": "212725263002001", | ||||||
|  |       "IsDelete": "false", | ||||||
|  |       "UpdateTime": "2023-03-02 19:46:13.3919053", | ||||||
|  |       "UpdateUser": "superAdmin", | ||||||
|  |       "UpdateUserId": "212725263002001" | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | { | ||||||
|  |   "RECORDS": [ | ||||||
|  |     { | ||||||
|  |       "Id": "212725263001001", | ||||||
|  |       "Code": "superAdmin", | ||||||
|  |       "Name": "超级管理员", | ||||||
|  |       "SortCode": "1" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "212725263001002", | ||||||
|  |       "Code": "admin", | ||||||
|  |       "Name": "业务管理员", | ||||||
|  |       "SortCode": "2" | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @@ -0,0 +1,34 @@ | |||||||
|  | { | ||||||
|  |   "RECORDS": [ | ||||||
|  |     { | ||||||
|  |       "Id": "212725263002001", | ||||||
|  |       "Account": "superAdmin", | ||||||
|  |       "LastLoginDevice": "PC", | ||||||
|  |       "LastLoginIp": "0.0.0.1", | ||||||
|  |       "LastLoginTime": "2023-03-03 21:18:43.7092169", | ||||||
|  |       "LatestLoginDevice": "PC", | ||||||
|  |       "LatestLoginIp": "0.0.0.1", | ||||||
|  |       "LatestLoginTime": "2023-03-03 21:19:16.1043309", | ||||||
|  |       "Password": "7DA385A25A98388E", | ||||||
|  |       "SortCode": "1", | ||||||
|  |       "UserEnable": "true", | ||||||
|  |       "IsDelete": "false", | ||||||
|  |       "UpdateTime": "2023-03-03 21:19:16.1202211" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "Id": "201725263002001", | ||||||
|  |       "Account": "admin", | ||||||
|  |       "LastLoginDevice": "PC", | ||||||
|  |       "LastLoginIp": "0.0.0.1", | ||||||
|  |       "LastLoginTime": "2023-03-03 18:20:49.1875384", | ||||||
|  |       "LatestLoginDevice": "PC", | ||||||
|  |       "LatestLoginIp": "0.0.0.1", | ||||||
|  |       "LatestLoginTime": "2023-03-03 18:23:08.6424099", | ||||||
|  |       "Password": "7DA385A25A98388E", | ||||||
|  |       "SortCode": "2", | ||||||
|  |       "UserEnable": "true", | ||||||
|  |       "IsDelete": "false", | ||||||
|  |       "UpdateTime": "2023-03-03 18:23:08.6727296" | ||||||
|  |     } | ||||||
|  |     ] | ||||||
|  | } | ||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 系统配置种子数据 | ||||||
|  | /// </summary> | ||||||
|  | public class SysConfigSeedData : ISqlSugarEntitySeedData<SysConfig> | ||||||
|  | { | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public IEnumerable<SysConfig> SeedData() | ||||||
|  |     { | ||||||
|  |         return SeedDataUtil.GetSeedData<SysConfig>("sys_config.json"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 关系表种子数据 | ||||||
|  | /// </summary> | ||||||
|  | public class SysRelationSeedData : ISqlSugarEntitySeedData<SysRelation> | ||||||
|  | { | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public IEnumerable<SysRelation> SeedData() | ||||||
|  |     { | ||||||
|  |         return SeedDataUtil.GetSeedData<SysRelation>("sys_relation.json"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 资源表种子数据 | ||||||
|  | /// </summary> | ||||||
|  | public class SysResourceSeedData : ISqlSugarEntitySeedData<SysResource> | ||||||
|  | { | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public IEnumerable<SysResource> SeedData() | ||||||
|  |     { | ||||||
|  |         return SeedDataUtil.GetSeedData<SysResource>("sys_resource.json"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 角色种子数据 | ||||||
|  | /// </summary> | ||||||
|  | public class SysRoleSeedData : ISqlSugarEntitySeedData<SysRole> | ||||||
|  | { | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public IEnumerable<SysRole> SeedData() | ||||||
|  |     { | ||||||
|  |         return SeedDataUtil.GetSeedData<SysRole>("sys_role.json"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 用户表种子数据 | ||||||
|  | /// </summary> | ||||||
|  | public class SysUserSeedData : ISqlSugarEntitySeedData<SysUser> | ||||||
|  | { | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public IEnumerable<SysUser> SeedData() | ||||||
|  |     { | ||||||
|  |         return SeedDataUtil.GetSeedData<SysUser>("sys_user.json"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 即时通讯集线器 | ||||||
|  | /// </summary> | ||||||
|  | public interface ISysHub | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 退出登录 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="context"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task Logout(object context); | ||||||
|  | } | ||||||
							
								
								
									
										118
									
								
								framework/ThingsGateway.Admin.Application/SignalR/Hub/SysHub.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								framework/ThingsGateway.Admin.Application/SignalR/Hub/SysHub.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion; | ||||||
|  | using Furion.InstantMessaging; | ||||||
|  | using Furion.Logging.Extensions; | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.Http.Connections.Features; | ||||||
|  | using Microsoft.AspNetCore.SignalR; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// <inheritdoc cref="ISysHub"/> | ||||||
|  | /// </summary> | ||||||
|  | [MapHub(HubConst.SysHubUrl)] | ||||||
|  | public class SysHub : Hub<ISysHub> | ||||||
|  | { | ||||||
|  |     readonly ILogger<ISysHub> _logger; | ||||||
|  |     /// <inheritdoc cref="ISysHub"/> | ||||||
|  |     public SysHub(ILogger<ISysHub> logger) | ||||||
|  |     { | ||||||
|  |         _logger = logger; | ||||||
|  |     } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 分隔符 | ||||||
|  |     /// </summary> | ||||||
|  |     public const string SYS_TrackingCircuitHandlerid = "SYS_TrackingCircuitHandlerid"; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 连接 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public override async Task OnConnectedAsync() | ||||||
|  |     { | ||||||
|  |         var feature = Context.Features.Get<IHttpContextFeature>(); | ||||||
|  |         var VerificatId = feature.HttpContext.Request.Headers[ClaimConst.VerificatId].FirstOrDefault().ToLong(); | ||||||
|  |  | ||||||
|  |         var userIdentifier = Context.UserIdentifier;//自定义的Id | ||||||
|  |         await UpdateVerificatAsync(userIdentifier, verificat: VerificatId);//更新cache | ||||||
|  |         await base.OnConnectedAsync(); | ||||||
|  |     } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 断开连接 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="exception"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public override async Task OnDisconnectedAsync(Exception exception) | ||||||
|  |     { | ||||||
|  |         var userIdentifier = Context.UserIdentifier;//自定义的Id | ||||||
|  |         await UpdateVerificatAsync(userIdentifier, false);//更新cache | ||||||
|  |         await base.OnDisconnectedAsync(exception); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #region 方法 | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 更新cache | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="userIdentifier">用户id</param> | ||||||
|  |     /// <param name="verificat">上线时的验证id</param> | ||||||
|  |     /// <param name="isConnect">是否是上线</param> | ||||||
|  |     private async Task UpdateVerificatAsync(string userIdentifier, bool isConnect = true, long verificat = 0) | ||||||
|  |     { | ||||||
|  |         var userId = userIdentifier.Split(SYS_TrackingCircuitHandlerid)[0].ToLong();//分割取第一个 | ||||||
|  |         if (userId > 0) | ||||||
|  |         { | ||||||
|  |             var _verificatService = App.GetService<IVerificatService>(); | ||||||
|  |             //获取cache当前用户的verificat信息列表 | ||||||
|  |             List<VerificatInfo> verificatInfos = await _verificatService.GetVerificatIdAsync(userId); | ||||||
|  |  | ||||||
|  |             if (verificatInfos != null) | ||||||
|  |             { | ||||||
|  |                 if (isConnect) | ||||||
|  |                 { | ||||||
|  |                     //获取cache中当前verificat | ||||||
|  |                     var verificatInfo = verificatInfos.Where(it => it.Id == verificat).FirstOrDefault(); | ||||||
|  |                     if (verificatInfo != null) | ||||||
|  |                     { | ||||||
|  |                         verificatInfo.ClientIds.Add(userIdentifier);//添加到客户端列表 | ||||||
|  |                         await _verificatService.SetVerificatIdAsync(userId, verificatInfos); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     //获取当前客户端ID所在的verificat信息 | ||||||
|  |                     var verificatInfo = verificatInfos.Where(it => it.ClientIds.Contains(userIdentifier)).FirstOrDefault(); | ||||||
|  |                     if (verificatInfo != null) | ||||||
|  |                     { | ||||||
|  |                         verificatInfo.ClientIds.RemoveWhere(it => it == userIdentifier);//从客户端列表删除 | ||||||
|  |                         await _verificatService.SetVerificatIdAsync(userId, verificatInfos); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             if (isConnect) | ||||||
|  |                 _logger.LogWarning($"未认证SignalR ID:{userIdentifier} 登录"); | ||||||
|  |             else | ||||||
|  |                 _logger.LogWarning($"未认证SignalR ID:{userIdentifier} 注销"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #endregion 方法 | ||||||
|  | } | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| //------------------------------------------------------------------------------ | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
| @@ -7,10 +8,15 @@ | |||||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
| 
 | 
 | ||||||
| using Microsoft.AspNetCore.Http.Connections.Features; | using Microsoft.AspNetCore.Http.Connections.Features; | ||||||
| using Microsoft.AspNetCore.SignalR; | using Microsoft.AspNetCore.SignalR; | ||||||
| 
 | 
 | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  | 
 | ||||||
|  | using Yitter.IdGenerator; | ||||||
|  | 
 | ||||||
| namespace ThingsGateway.Admin.Application; | namespace ThingsGateway.Admin.Application; | ||||||
| 
 | 
 | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -26,7 +32,7 @@ public class UserIdProvider : IUserIdProvider | |||||||
| 
 | 
 | ||||||
|         if (UserId > 0) |         if (UserId > 0) | ||||||
|         { |         { | ||||||
|             return $"{UserId}{SysHub.Separate}{CommonUtils.GetSingleId()}";//返回用户ID |             return $"{UserId}{SysHub.SYS_TrackingCircuitHandlerid}{YitIdHelper.NextId()}";//返回用户ID | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return connection.ConnectionId; |         return connection.ConnectionId; | ||||||
							
								
								
									
										43
									
								
								framework/ThingsGateway.Admin.Application/Startup.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								framework/ThingsGateway.Admin.Application/Startup.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion; | ||||||
|  |  | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// AppStartup启动类 | ||||||
|  | /// </summary> | ||||||
|  | public class Startup : AppStartup | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 | ||||||
|  |     /// </summary> | ||||||
|  |     public void ConfigureServices(IServiceCollection services) | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         // 任务调度 | ||||||
|  |         services.AddSchedule(options => | ||||||
|  |         { | ||||||
|  |             options.AddPersistence<JobPersistence>(); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         //事件总线 | ||||||
|  |         services.AddEventBus(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,55 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  | using Furion.EventBus; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 认证模块事件总线 | ||||||
|  | /// </summary> | ||||||
|  | public class AuthEventSubscriber : IEventSubscriber, ISingleton | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 登录事件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="context"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     [EventSubscribe(EventSubscriberConst.Login)] | ||||||
|  |     public async Task Login(EventHandlerExecutingContext context) | ||||||
|  |     { | ||||||
|  |         var loginEvent = (LoginEvent)context.Source.Payload;//获取参数 | ||||||
|  |         var sysUser = loginEvent.SysUser; | ||||||
|  |         var db = DbContext.Db.CopyNew(); | ||||||
|  |  | ||||||
|  |         #region 重新赋值属性,设置本次登录信息为最新的信息 | ||||||
|  |  | ||||||
|  |         db.Tracking(sysUser);//创建跟踪,只更新修改字段 | ||||||
|  |         sysUser.LastLoginDevice = sysUser.LatestLoginDevice; | ||||||
|  |         sysUser.LastLoginIp = sysUser.LatestLoginIp; | ||||||
|  |         sysUser.LastLoginTime = sysUser.LatestLoginTime; | ||||||
|  |         sysUser.LatestLoginDevice = loginEvent.Device.ToString(); | ||||||
|  |         sysUser.LatestLoginIp = loginEvent.Ip; | ||||||
|  |         sysUser.LatestLoginTime = loginEvent.DateTime; | ||||||
|  |  | ||||||
|  |         #endregion 重新赋值属性,设置本次登录信息为最新的信息 | ||||||
|  |  | ||||||
|  |         //更新用户信息 | ||||||
|  |         if (await db.UpdateableWithAttr(sysUser).ExecuteCommandAsync() > 0) | ||||||
|  |         { | ||||||
|  |             CacheStatic.Cache.Set(CacheConst.CACHE_SYSUSER + sysUser.Id, sysUser, false); //更新Cache信息 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,59 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  | using Furion.EventBus; | ||||||
|  |  | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 用户模块事件总线 | ||||||
|  | /// </summary> | ||||||
|  | public class UserEventSubscriber : IEventSubscriber, ISingleton | ||||||
|  | { | ||||||
|  |     private readonly IServiceProvider _services; | ||||||
|  |     /// <summary> | ||||||
|  |     /// <inheritdoc cref="UserEventSubscriber"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="services"></param> | ||||||
|  |     public UserEventSubscriber(IServiceProvider services) | ||||||
|  |     { | ||||||
|  |         _services = services; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据角色ID列表清除用户缓存 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="context"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     [EventSubscribe(EventSubscriberConst.ClearUserCache)] | ||||||
|  |     public async Task DeleteUserCacheByRoleIds(EventHandlerExecutingContext context) | ||||||
|  |     { | ||||||
|  |         var roleIds = (List<long>)context.Source.Payload;//获取角色ID | ||||||
|  |         // 创建新的作用域 | ||||||
|  |         using var scope = _services.CreateScope(); | ||||||
|  |         // 解析角色服务 | ||||||
|  |         var relationService = scope.ServiceProvider.GetRequiredService<IRelationService>(); | ||||||
|  |         //获取用户和角色关系 | ||||||
|  |         var relations = await relationService.GetRelationListByTargetIdListAndCategoryAsync(roleIds.Select(it => it.ToString()).ToList(), CateGoryConst.Relation_SYS_USER_HAS_ROLE); | ||||||
|  |         if (relations.Count > 0) | ||||||
|  |         { | ||||||
|  |             var userIds = relations.Select(it => it.ObjectId).ToArray();//用户ID列表 | ||||||
|  |             // 解析用户服务 | ||||||
|  |             var userService = scope.ServiceProvider.GetRequiredService<ISysUserService>(); | ||||||
|  |             //从缓存中删除 | ||||||
|  |             userService.DeleteUserFromCache(userIds); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,240 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion; | ||||||
|  | using Furion.DataEncryption; | ||||||
|  | using Furion.EventBus; | ||||||
|  | using Furion.FriendlyException; | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.Authentication; | ||||||
|  | using Microsoft.AspNetCore.Authentication.Cookies; | ||||||
|  | using Microsoft.AspNetCore.Http; | ||||||
|  |  | ||||||
|  | using System.Security.Claims; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | using Yitter.IdGenerator; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <inheritdoc cref="IAuthService"/> | ||||||
|  | public class AuthService : IAuthService | ||||||
|  | { | ||||||
|  |     private readonly IConfigService _configService; | ||||||
|  |     private readonly IEventPublisher _eventPublisher; | ||||||
|  |     private readonly INoticeService _noticeService; | ||||||
|  |     private readonly ISysUserService _userService; | ||||||
|  |     private readonly IVerificatService _verificatService; | ||||||
|  |  | ||||||
|  |     /// <inheritdoc cref="IAuthService"/> | ||||||
|  |     public AuthService( | ||||||
|  |                        IEventPublisher eventPublisher, | ||||||
|  |                        ISysUserService userService, | ||||||
|  |                        IConfigService configService, | ||||||
|  |                        IVerificatService verificatService, | ||||||
|  |                         INoticeService noticeService | ||||||
|  |         ) | ||||||
|  |     { | ||||||
|  |         _eventPublisher = eventPublisher; | ||||||
|  |         _userService = userService; | ||||||
|  |         _configService = configService; | ||||||
|  |         _verificatService = verificatService; | ||||||
|  |         _noticeService = noticeService; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public ValidCodeOutput GetCaptchaInfo() | ||||||
|  |     { | ||||||
|  |         //生成验证码 | ||||||
|  |         var captchInfo = new Random().Next(1111, 9999).ToString(); | ||||||
|  |         //生成请求号,并将验证码放入cache | ||||||
|  |         var reqNo = YitIdHelper.NextId(); | ||||||
|  |         //插入cache | ||||||
|  |         CacheStatic.Cache.Set(CacheConst.LOGIN_CAPTCHA + reqNo, captchInfo, TimeSpan.FromMinutes(1), false); | ||||||
|  |         //返回验证码和请求号 | ||||||
|  |         return new ValidCodeOutput { CodeValue = captchInfo, ValidCodeReqNo = reqNo }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public Task<SysUser> GetLoginUserAsync() | ||||||
|  |     { | ||||||
|  |         return _userService.GetUserByIdAsync(UserManager.UserId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<LoginOutput> LoginAsync(LoginInput input) | ||||||
|  |     { | ||||||
|  |         //判断是否有验证码 | ||||||
|  |         var sysBase = await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_CAPTCHA_OPEN); | ||||||
|  |  | ||||||
|  |         if (sysBase != null)//如果有这个配置项 | ||||||
|  |         { | ||||||
|  |             if (sysBase.ConfigValue.ToBoolean())//如果需要验证码 | ||||||
|  |             { | ||||||
|  |                 //如果没填验证码,提示验证码不能为空 | ||||||
|  |                 if (input.ValidCode.IsNullOrEmpty() || input.ValidCodeReqNo == 0) throw Oops.Bah("验证码不能为空").StatusCode(410); | ||||||
|  |                 ValidValidCode(input.ValidCode, input.ValidCodeReqNo);//校验验证码 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var password = DESCEncryption.Decrypt(input.Password, DESCKeyConst.DESCKey);  // 解密 | ||||||
|  |         var userInfo = await _userService.GetUserByAccountAsync(input.Account);//获取用户信息 | ||||||
|  |         if (userInfo == null) throw Oops.Bah("用户不存在");//用户不存在 | ||||||
|  |         if (userInfo.Password != password) throw Oops.Bah("账号密码错误");//账号密码错误 | ||||||
|  |         return await LoginAsync(userInfo, input.Device); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task LogoutAsync() | ||||||
|  |     { | ||||||
|  |         //获取用户信息 | ||||||
|  |         var userinfo = await _userService.GetUserByAccountAsync(UserManager.UserAccount); | ||||||
|  |         if (userinfo != null) | ||||||
|  |         { | ||||||
|  |             LoginEvent loginEvent = new() | ||||||
|  |             { | ||||||
|  |                 Ip = App.HttpContext.GetRemoteIpAddressToIPv4(), | ||||||
|  |                 SysUser = userinfo, | ||||||
|  |                 VerificatId = UserManager.VerificatId.ToLong(), | ||||||
|  |             }; | ||||||
|  |             await RemoveVerificatAsync(loginEvent);//移除验证Id | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 校验验证码方法 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="validCode">验证码</param> | ||||||
|  |     /// <param name="validCodeReqNo">请求号</param> | ||||||
|  |     /// <param name="isDelete">是否从Cache删除</param> | ||||||
|  |     private static void ValidValidCode(string validCode, long validCodeReqNo, bool isDelete = true) | ||||||
|  |     { | ||||||
|  |         var code = CacheStatic.Cache.Get<string>(CacheConst.LOGIN_CAPTCHA + validCodeReqNo, false);//从cache拿数据 | ||||||
|  |         if (isDelete) CacheStatic.Cache.Remove(CacheConst.LOGIN_CAPTCHA + validCodeReqNo);//删除验证码 | ||||||
|  |         if (code != null)//如果有 | ||||||
|  |         { | ||||||
|  |             //验证码如果不匹配直接抛错误,这里忽略大小写 | ||||||
|  |             if (validCode.ToLower() != code.ToLower()) throw Oops.Bah("验证码错误"); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             throw Oops.Bah("验证码不能为空");//抛出验证码不能为空 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 执行B端登录 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="sysUser">用户信息</param> | ||||||
|  |     /// <param name="device">登录设备</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     private async Task<LoginOutput> LoginAsync(SysUser sysUser, AuthDeviceTypeEnum device) | ||||||
|  |     { | ||||||
|  |         if (sysUser.UserEnable == false) throw Oops.Bah("账号已停用");//账号已停用 | ||||||
|  |  | ||||||
|  |         var sysBase = await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_VERIFICAT_EXPIRES); | ||||||
|  |         var sessionid = YitIdHelper.NextId(); | ||||||
|  |         var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); | ||||||
|  |         identity.AddClaim(new Claim(ClaimConst.VerificatId, sessionid.ToString())); | ||||||
|  |         identity.AddClaim(new Claim(ClaimConst.UserId, sysUser.Id.ToString())); | ||||||
|  |         identity.AddClaim(new Claim(ClaimConst.Account, sysUser.Account)); | ||||||
|  |         identity.AddClaim(new Claim(ClaimConst.IsSuperAdmin, sysUser.RoleCodeList.Contains(RoleConst.SuperAdmin).ToString())); | ||||||
|  |         identity.AddClaim(new Claim(ClaimConst.IsOpenApi, false.ToString())); | ||||||
|  |  | ||||||
|  |         var config = sysBase.ConfigValue.ToInt(2880); | ||||||
|  |         var diffTime = SysDateTimeExtensions.CurrentDateTime.AddMinutes(config); | ||||||
|  |         await App.HttpContext.SignInAsync(new ClaimsPrincipal(identity), new AuthenticationProperties() | ||||||
|  |         { | ||||||
|  |             IsPersistent = true, | ||||||
|  |             ExpiresUtc = diffTime, | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         //登录事件参数 | ||||||
|  |         var loginEvent = new LoginEvent | ||||||
|  |         { | ||||||
|  |             Ip = App.HttpContext.GetRemoteIpAddressToIPv4(), | ||||||
|  |             Device = device, | ||||||
|  |             Expire = config, | ||||||
|  |             SysUser = sysUser, | ||||||
|  |             VerificatId = sessionid, | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         await SetVerificatAsync(loginEvent);//写入verificat | ||||||
|  |  | ||||||
|  |         await _eventPublisher.PublishAsync(EventSubscriberConst.Login, loginEvent); //发布登录事件总线 | ||||||
|  |         return new LoginOutput { VerificatId = sessionid, Account = sysUser.Account }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private async Task RemoveVerificatAsync(LoginEvent loginEvent) | ||||||
|  |     { | ||||||
|  |         //获取verificat列表 | ||||||
|  |         List<VerificatInfo> verificatInfos = await _verificatService.GetVerificatIdAsync(loginEvent.SysUser.Id); | ||||||
|  |         if (verificatInfos != null) | ||||||
|  |         { | ||||||
|  |             //获取当前用户的verificat | ||||||
|  |             var verificat = verificatInfos.Where(it => it.Id == loginEvent.VerificatId).FirstOrDefault(); | ||||||
|  |             if (verificat != null) | ||||||
|  |                 verificatInfos.Remove(verificat); | ||||||
|  |             //更新verificat列表 | ||||||
|  |             await _verificatService.SetVerificatIdAsync(loginEvent.SysUser.Id, verificatInfos); | ||||||
|  |         } | ||||||
|  |         await App.HttpContext?.SignOutAsync(); | ||||||
|  |         App.HttpContext?.SignoutToSwagger(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 写入验证信息到缓存 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="loginEvent"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     private async Task SetVerificatAsync(LoginEvent loginEvent) | ||||||
|  |     { | ||||||
|  |         //获取verificat列表 | ||||||
|  |         List<VerificatInfo> verificatInfos = await _verificatService.GetVerificatIdAsync(loginEvent.SysUser.Id); | ||||||
|  |         var verificatTimeout = loginEvent.DateTime.AddMinutes(loginEvent.Expire); | ||||||
|  |         //生成verificat信息 | ||||||
|  |         var verificatInfo = new VerificatInfo | ||||||
|  |         { | ||||||
|  |             Device = loginEvent.Device.ToString(), | ||||||
|  |             Expire = loginEvent.Expire, | ||||||
|  |             VerificatTimeout = verificatTimeout, | ||||||
|  |             Id = loginEvent.VerificatId, | ||||||
|  |             UserId = loginEvent.SysUser.Id, | ||||||
|  |         }; | ||||||
|  |         if (verificatInfos != null) | ||||||
|  |         { | ||||||
|  |             bool isSingle = false;//默认不开启单用户登录 | ||||||
|  |  | ||||||
|  |             var singleConfig = await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SINGLE_OPEN);//获取系统单用户登录选项 | ||||||
|  |             if (singleConfig != null) isSingle = singleConfig.ConfigValue.ToBoolean();//如果配置不为空则设置单用户登录选项为系统配置的值 | ||||||
|  |             if (isSingle)//判断是否单用户登录 | ||||||
|  |             { | ||||||
|  |                 await _noticeService.LogoutAsync(loginEvent.SysUser.Id, verificatInfos.Where(it => it.Device == loginEvent.Device.ToString()).ToList(), "该账号已在别处登录!");//通知其他用户下线 | ||||||
|  |                 verificatInfos = verificatInfos.Where(it => it.Device != loginEvent.Device.ToString()).ToList();//去掉当前登录类型 | ||||||
|  |                 verificatInfos.Add(verificatInfo);//添加到列表 | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 verificatInfos.Add(verificatInfo); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             verificatInfos = new List<VerificatInfo> { verificatInfo };//直接就一个 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         //添加到verificat列表 | ||||||
|  |         await _verificatService.SetVerificatIdAsync(loginEvent.SysUser.Id, verificatInfos); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,78 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using System.ComponentModel; | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 登录输入参数 | ||||||
|  | /// </summary> | ||||||
|  | public class LoginInput : ValidCodeInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 账号 | ||||||
|  |     ///</summary> | ||||||
|  |     [Required(ErrorMessage = "账号不能为空"), MinLength(3, ErrorMessage = "账号不能少于4个字符")] | ||||||
|  |     public string Account { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 设备类型,默认PC | ||||||
|  |     /// </summary> | ||||||
|  |     /// <example>0</example> | ||||||
|  |     public AuthDeviceTypeEnum Device { get; set; } = AuthDeviceTypeEnum.PC; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 密码 | ||||||
|  |     ///</summary> | ||||||
|  |     [Required(ErrorMessage = "密码不能为空"), MinLength(3, ErrorMessage = "密码不能少于3个字符")] | ||||||
|  |     public string Password { get; set; } | ||||||
|  | } | ||||||
|  | /// <summary> | ||||||
|  | /// 验证码输入 | ||||||
|  | /// </summary> | ||||||
|  | public class ValidCodeInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 验证码 | ||||||
|  |     /// </summary> | ||||||
|  |     public string ValidCode { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 请求号 | ||||||
|  |     /// </summary> | ||||||
|  |     public long ValidCodeReqNo { get; set; } | ||||||
|  | } | ||||||
|  | /// <summary> | ||||||
|  | /// 登录设备类型枚举 | ||||||
|  | /// </summary> | ||||||
|  | public enum AuthDeviceTypeEnum | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// PC端 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("PC端")] | ||||||
|  |     PC, | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 移动端 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("移动端")] | ||||||
|  |     APP, | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Api | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("Api")] | ||||||
|  |     Api, | ||||||
|  | } | ||||||
| @@ -0,0 +1,51 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 登录返回参数 | ||||||
|  | /// </summary> | ||||||
|  | public class BaseLoginOutput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 账号 | ||||||
|  |     /// </summary> | ||||||
|  |     public string Account { get; set; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 验证ID | ||||||
|  |     /// </summary> | ||||||
|  |     public long VerificatId { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 验证码值返回 | ||||||
|  | /// </summary> | ||||||
|  | public class ValidCodeOutput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 验证码值 | ||||||
|  |     /// </summary> | ||||||
|  |     public string CodeValue { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 验证码请求号 | ||||||
|  |     /// </summary> | ||||||
|  |     public long ValidCodeReqNo { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 登录返回参数 | ||||||
|  | /// </summary> | ||||||
|  | public class LoginOutput : BaseLoginOutput | ||||||
|  | { | ||||||
|  | } | ||||||
| @@ -0,0 +1,51 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 登录事件参数 | ||||||
|  | /// </summary> | ||||||
|  | public class LoginEvent | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 时间 | ||||||
|  |     /// </summary> | ||||||
|  |     public DateTime DateTime = SysDateTimeExtensions.CurrentDateTime; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 登录设备 | ||||||
|  |     /// </summary> | ||||||
|  |     public AuthDeviceTypeEnum Device { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 过期时间(分) | ||||||
|  |     /// </summary> | ||||||
|  |     public int Expire { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Ip地址 | ||||||
|  |     /// </summary> | ||||||
|  |     public string Ip { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户信息 | ||||||
|  |     /// </summary> | ||||||
|  |     public SysUser SysUser { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 验证Id | ||||||
|  |     /// </summary> | ||||||
|  |     public long VerificatId { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,46 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 权限校验服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface IAuthService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 生成验证码 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     ValidCodeOutput GetCaptchaInfo(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取登录用户信息 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<SysUser> GetLoginUserAsync(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 登录 | ||||||
|  |     /// </summary> | ||||||
|  |     Task<LoginOutput> LoginAsync(LoginInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 退出登录 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task LogoutAsync(); | ||||||
|  | } | ||||||
| @@ -0,0 +1,165 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  | using Furion.FriendlyException; | ||||||
|  |  | ||||||
|  | using Mapster; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// <inheritdoc cref="IButtonService"/> | ||||||
|  | /// </summary> | ||||||
|  | [Injection(Proxy = typeof(OperDispatchProxy))] | ||||||
|  | public class ButtonService : DbRepository<SysResource>, IButtonService | ||||||
|  | { | ||||||
|  |     private readonly IRelationService _relationService; | ||||||
|  |     private readonly IResourceService _resourceService; | ||||||
|  |  | ||||||
|  |     /// <inheritdoc cref="IButtonService"/> | ||||||
|  |     public ButtonService( | ||||||
|  |         IResourceService resourceService, | ||||||
|  |         IRelationService relationService | ||||||
|  |         ) | ||||||
|  |     { | ||||||
|  |         _resourceService = resourceService; | ||||||
|  |         _relationService = relationService; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public async Task AddAsync(ButtonAddInput input) | ||||||
|  |     { | ||||||
|  |         await CheckInputAsync(input);//检查参数 | ||||||
|  |         var sysResource = input.Adapt<SysResource>();//实体转换 | ||||||
|  |         if (await InsertAsync(sysResource))//插入数据 | ||||||
|  |             _resourceService.RefreshCache(ResourceCategoryEnum.BUTTON);//刷新缓存 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     [OperDesc("删除按钮")] | ||||||
|  |     public async Task DeleteAsync(params long[] input) | ||||||
|  |     { | ||||||
|  |         //获取所有ID | ||||||
|  |         var ids = input.ToList(); | ||||||
|  |         //获取所有按钮集合 | ||||||
|  |         var buttonList = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.BUTTON); | ||||||
|  |  | ||||||
|  |         #region 处理关系表角色资源信息 | ||||||
|  |  | ||||||
|  |         //获取所有菜单集合 | ||||||
|  |         var menuList = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.MENU); | ||||||
|  |         //获取按钮的父菜单id集合 | ||||||
|  |         var parentIds = buttonList.Where(it => ids.Contains(it.Id)).Select(it => it.ParentId.ToString()).ToList(); | ||||||
|  |         //获取关系表分类为SYS_ROLE_HAS_RESOURCE数据 | ||||||
|  |         var roleResources = await _relationService.GetRelationByCategoryAsync(CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE); | ||||||
|  |         //获取相关关系表数据 | ||||||
|  |         var relationList = roleResources | ||||||
|  |                 .Where(it => parentIds.Contains(it.TargetId))//目标ID是父ID中 | ||||||
|  |                 .Where(it => it.ExtJson != null).ToList();//扩展信息不为空 | ||||||
|  |  | ||||||
|  |         //遍历关系表 | ||||||
|  |         relationList.ForEach(it => | ||||||
|  |         { | ||||||
|  |             var relationRoleResuorce = it.ExtJson.ToJsonWithT<RelationRoleResuorce>();//拓展信息转实体 | ||||||
|  |             var buttonInfo = relationRoleResuorce.ButtonInfo;//获取按钮信息 | ||||||
|  |             if (buttonInfo.Count > 0) | ||||||
|  |             { | ||||||
|  |                 var diffArr = buttonInfo.Where(it => !buttonInfo.Contains(it)).ToList(); //找出不同的元素(即交集的补集) | ||||||
|  |                 relationRoleResuorce.ButtonInfo = diffArr;//重新赋值按钮信息 | ||||||
|  |                 it.ExtJson = relationRoleResuorce.ToJsonString();//重新赋值拓展信息 | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         #endregion 处理关系表角色资源信息 | ||||||
|  |  | ||||||
|  |         //事务 | ||||||
|  |         var result = await itenant.UseTranAsync(async () => | ||||||
|  |         { | ||||||
|  |             await DeleteByIdsAsync(ids.Cast<object>().ToArray());//删除按钮 | ||||||
|  |             if (relationList.Count > 0) | ||||||
|  |             { | ||||||
|  |                 await Context.Updateable(relationList).UpdateColumns(it => it.ExtJson).ExecuteCommandAsync();//修改拓展信息 | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         if (result.IsSuccess)//如果成功了 | ||||||
|  |         { | ||||||
|  |             _resourceService.RefreshCache(ResourceCategoryEnum.BUTTON);//资源表按钮刷新缓存 | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             throw Oops.Oh(result.ErrorMessage); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     [OperDesc("编辑按钮")] | ||||||
|  |     public async Task EditAsync(ButtonEditInput input) | ||||||
|  |     { | ||||||
|  |         await CheckInputAsync(input);//检查参数 | ||||||
|  |         var sysResource = input.Adapt<SysResource>();//实体转换 | ||||||
|  |                                                      //事务 | ||||||
|  |         var result = await itenant.UseTranAsync(async () => | ||||||
|  |         { | ||||||
|  |             await UpdateAsync(sysResource); //更新按钮 | ||||||
|  |         }); | ||||||
|  |         if (result.IsSuccess)//如果成功了 | ||||||
|  |         { | ||||||
|  |             _resourceService.RefreshCache(ResourceCategoryEnum.BUTTON);//资源表按钮刷新缓存 | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             throw Oops.Oh(result.ErrorMessage); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<SqlSugarPagedList<SysResource>> PageAsync(ButtonPageInput input) | ||||||
|  |     { | ||||||
|  |         var query = Context.Queryable<SysResource>() | ||||||
|  |                          .Where(it => it.ParentId == input.ParentId && it.Category == ResourceCategoryEnum.BUTTON) | ||||||
|  |                          .WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Title.Contains(input.SearchKey) || it.Component.Contains(input.SearchKey));//根据关键字查询 | ||||||
|  |         for (int i = 0; i < input.SortField.Count; i++) | ||||||
|  |         { | ||||||
|  |             query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}"); | ||||||
|  |         } | ||||||
|  |         query = query.OrderBy(it => it.SortCode);//排序 | ||||||
|  |  | ||||||
|  |         var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页 | ||||||
|  |         return pageInfo; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #region 方法 | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 检查输入参数 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="sysResource"></param> | ||||||
|  |     private async Task CheckInputAsync(SysResource sysResource) | ||||||
|  |     { | ||||||
|  |         //获取所有按钮和菜单 | ||||||
|  |         var buttonList = await _resourceService.GetListByCategorysAsync(new List<ResourceCategoryEnum> { ResourceCategoryEnum.BUTTON, ResourceCategoryEnum.MENU }); | ||||||
|  |         //判断code是否重复 | ||||||
|  |         if (buttonList.Any(it => it.Code == sysResource.Code && it.Id != sysResource.Id)) | ||||||
|  |             throw Oops.Bah($"存在重复的按钮编码:{sysResource.Code}"); | ||||||
|  |         //判断菜单是否存在 | ||||||
|  |         if (!buttonList.Any(it => it.Id == sysResource.ParentId)) | ||||||
|  |             throw Oops.Bah($"不存在的父级菜单:{sysResource.ParentId}"); | ||||||
|  |         sysResource.Category = ResourceCategoryEnum.BUTTON;//设置分类为按钮 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     #endregion 方法 | ||||||
|  | } | ||||||
| @@ -0,0 +1,63 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 添加按钮参数 | ||||||
|  | /// </summary> | ||||||
|  | public class ButtonAddInput : SysResource | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 编码 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "Code不能为空")] | ||||||
|  |     public override string Code { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 父ID | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "ParentId不能为空")] | ||||||
|  |     public override long ParentId { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 标题 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "Title不能为空")] | ||||||
|  |     public override string Title { get; set; } | ||||||
|  | } | ||||||
|  | /// <summary> | ||||||
|  | /// 按钮分页 | ||||||
|  | /// </summary> | ||||||
|  | public class ButtonPageInput : BasePageInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 父ID | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "ParentId不能为空")] | ||||||
|  |     public long? ParentId { get; set; } | ||||||
|  | } | ||||||
|  | /// <summary> | ||||||
|  | /// 按钮编辑 | ||||||
|  | /// </summary> | ||||||
|  | public class ButtonEditInput : ButtonAddInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// ID | ||||||
|  |     /// </summary> | ||||||
|  |     [MinValue(1, ErrorMessage = "Id不能为空")] | ||||||
|  |     public override long Id { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,53 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 权限按钮服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface IButtonService : ITransient | ||||||
|  | { | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加按钮 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">添加参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task AddAsync(ButtonAddInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 删除按钮 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">删除参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task DeleteAsync(params long[] input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 编辑按钮 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">编辑参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task EditAsync(ButtonEditInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 按钮分页查询 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">查询条件</param> | ||||||
|  |     /// <returns>按钮分页列表</returns> | ||||||
|  |     Task<SqlSugarPagedList<SysResource>> PageAsync(ButtonPageInput input); | ||||||
|  | } | ||||||
| @@ -0,0 +1,132 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  | using Furion.FriendlyException; | ||||||
|  |  | ||||||
|  | using Mapster; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <inheritdoc cref="IConfigService"/> | ||||||
|  | [Injection(Proxy = typeof(OperDispatchProxy))] | ||||||
|  | public class ConfigService : DbRepository<SysConfig>, IConfigService | ||||||
|  | { | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("编辑网关系统配置")] | ||||||
|  |     public async Task EditBatchAsync(List<SysConfig> sysConfigs) | ||||||
|  |     { | ||||||
|  |         if (await UpdateRangeAsync(sysConfigs)) | ||||||
|  |             RefreshCache(sysConfigs.FirstOrDefault()?.Category);//刷新缓存 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("添加配置项")] | ||||||
|  |     public async Task AddAsync(ConfigAddInput input) | ||||||
|  |     { | ||||||
|  |         await CheckInputAsync(input);//检查 | ||||||
|  |         var sysConfig = input.Adapt<SysConfig>();//实体转换 | ||||||
|  |         if (await InsertAsync(sysConfig))//插入数据) | ||||||
|  |             RefreshCache(input.Category);//刷新缓存 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("删除配置项")] | ||||||
|  |     public async Task DeleteAsync(params long[] input) | ||||||
|  |     { | ||||||
|  |         await AsDeleteable().Where(it => input.Contains(it.Id)).ExecuteCommandAsync(); | ||||||
|  |         RefreshCache(ConfigConst.SYS_CONFIGOTHER);//刷新缓存 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("编辑配置项")] | ||||||
|  |     public async Task EditAsync(ConfigEditInput input) | ||||||
|  |     { | ||||||
|  |         await CheckInputAsync(input); | ||||||
|  |         var sysConfig = input.Adapt<SysConfig>();//实体转换 | ||||||
|  |         if (await UpdateAsync(sysConfig))//更新数据 | ||||||
|  |             RefreshCache(input.Category);//刷新缓存 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<SysConfig> GetByConfigKeyAsync(string category, string configKey) | ||||||
|  |     { | ||||||
|  |         var configList = await GetListByCategoryAsync(category);//获取系统配置列表 | ||||||
|  |         return configList.FirstOrDefault(it => it.ConfigKey == configKey);//根据configkey获取对应值 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<SysConfig>> GetListByCategoryAsync(string category) | ||||||
|  |     { | ||||||
|  |         //先从Cache拿,需要获取新的对象,避免操作导致缓存中对象改变 | ||||||
|  |         var configList = CacheStatic.Cache.Get<List<SysConfig>>(CacheConst.SYS_CONFIGCATEGORY + category, true); | ||||||
|  |         if (configList == null) | ||||||
|  |         { | ||||||
|  |             //cache没有再去数据可拿 | ||||||
|  |             configList = await Context.Queryable<SysConfig>().Where(it => it.Category == category).OrderBy(it => it.SortCode).ToListAsync();//获取系统配置列表 | ||||||
|  |             if (configList.Count > 0) | ||||||
|  |             { | ||||||
|  |                 CacheStatic.Cache.Set(CacheConst.SYS_CONFIGCATEGORY + category, configList, true);//如果不为空,插入cache | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return configList; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<SqlSugarPagedList<SysConfig>> PageAsync(ConfigPageInput input) | ||||||
|  |     { | ||||||
|  |         var query = Context.Queryable<SysConfig>() | ||||||
|  |                          .Where(it => it.Category == ConfigConst.SYS_CONFIGOTHER)//自定义配置 | ||||||
|  |                          .WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.ConfigKey.Contains(input.SearchKey) || it.ConfigKey.Contains(input.SearchKey)); | ||||||
|  |         //根据关键字查询 | ||||||
|  |         for (int i = 0; i < input.SortField.Count; i++) | ||||||
|  |         { | ||||||
|  |             query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}"); | ||||||
|  |         } | ||||||
|  |         query = query.OrderBy(it => it.SortCode);//排序 | ||||||
|  |  | ||||||
|  |         var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页 | ||||||
|  |         return pageInfo; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #region 方法 | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 检查输入参数,并设置分类为自定义分类 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="sysConfig"></param> | ||||||
|  |     private async Task CheckInputAsync(SysConfig sysConfig) | ||||||
|  |     { | ||||||
|  |         var configs = await GetListByCategoryAsync(sysConfig.Category);//获取全部字典 | ||||||
|  |         var hasSameKey = configs.Any(it => it.ConfigKey == sysConfig.ConfigKey && it.Id != sysConfig.Id); | ||||||
|  |         //判断是否从存在重复字典名 | ||||||
|  |         if (hasSameKey) | ||||||
|  |         { | ||||||
|  |             throw Oops.Bah($"存在重复的配置键:{sysConfig.ConfigKey}"); | ||||||
|  |         } | ||||||
|  |         sysConfig.Category = ConfigConst.SYS_CONFIGOTHER; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 刷新缓存 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="category">分类</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     private void RefreshCache(string category) | ||||||
|  |     { | ||||||
|  |         CacheStatic.Cache.Remove(CacheConst.SYS_CONFIGCATEGORY + category);//cache删除 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #endregion 方法 | ||||||
|  | } | ||||||
| @@ -0,0 +1,67 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using System.ComponentModel; | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 添加配置参数 | ||||||
|  | /// </summary> | ||||||
|  | public class ConfigAddInput : SysConfig | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 分类 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "Category不能为空")] | ||||||
|  |     public override string Category { get; set; } = ConfigConst.SYS_CONFIGOTHER; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置键 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "configKey不能为空")] | ||||||
|  |     public override string ConfigKey { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置值 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "ConfigValue不能为空")] | ||||||
|  |     public override string ConfigValue { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 编辑配置参数 | ||||||
|  | /// </summary> | ||||||
|  | public class ConfigEditInput : ConfigAddInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// ID | ||||||
|  |     /// </summary> | ||||||
|  |     [MinValue(1, ErrorMessage = "Id不能为空")] | ||||||
|  |     public override long Id { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 配置分页参数 | ||||||
|  | /// </summary> | ||||||
|  | public class ConfigPageInput : BasePageInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 分类 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("分类")] | ||||||
|  |     public string Category { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -0,0 +1,72 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 系统配置服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface IConfigService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 批量编辑系统配置 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configs">配置列表</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task EditBatchAsync(List<SysConfig> configs); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 新增自定义配置 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">新增参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task AddAsync(ConfigAddInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 删除自定义配置 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">删除</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task DeleteAsync(params long[] input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 修改自定义配置 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">修改参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task EditAsync(ConfigEditInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据分类和配置键获配置 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="category">分类</param> | ||||||
|  |     /// <param name="configKey">配置键</param> | ||||||
|  |     /// <returns>配置信息</returns> | ||||||
|  |     Task<SysConfig> GetByConfigKeyAsync(string category, string configKey); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据分类获取配置列表 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="category">分类名称</param> | ||||||
|  |     /// <returns>配置列表</returns> | ||||||
|  |     Task<List<SysConfig>> GetListByCategoryAsync(string category); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 分页查询自定义配置 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">查询参数</param> | ||||||
|  |     /// <returns>其他配置列表</returns> | ||||||
|  |     Task<SqlSugarPagedList<SysConfig>> PageAsync(ConfigPageInput input); | ||||||
|  | } | ||||||
| @@ -0,0 +1,38 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.FriendlyException; | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.Components.Forms; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// <inheritdoc cref="IFileService"/> | ||||||
|  | /// </summary> | ||||||
|  | public class FileService : IFileService | ||||||
|  | { | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public void ImportVerification(IBrowserFile file, int maxSzie = 300, string[] allowTypes = null) | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |         if (file == null) throw Oops.Bah("文件不能为空"); | ||||||
|  |         if (file.Size > maxSzie * 1024 * 1024) throw Oops.Bah($"文件大小不允许超过{maxSzie}M"); | ||||||
|  |         var fileSuffix = Path.GetExtension(file.Name).ToLower().Split(".")[1]; // 文件后缀 | ||||||
|  |         string[] allowTypeS = allowTypes ?? new string[] { "xlsx" };//允许上传的文件类型 | ||||||
|  |         if (!allowTypeS.Contains(fileSuffix)) throw Oops.Bah(errorMessage: "文件格式错误"); | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,35 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.Components.Forms; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 文件管理服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface IFileService : ITransient | ||||||
|  | { | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 验证上传文件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="file">文件</param> | ||||||
|  |     /// <param name="maxSzie">最大体积(M)</param> | ||||||
|  |     /// <param name="allowTypes">允许上传类型</param> | ||||||
|  |     void ImportVerification(IBrowserFile file, int maxSzie = 30, string[] allowTypes = null); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,93 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 添加菜单参数 | ||||||
|  | /// </summary> | ||||||
|  | public class MenuAddInput : SysResource, IValidatableObject | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 路径 | ||||||
|  |     /// </summary> | ||||||
|  |     public override string Component { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 图标 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "Icon不能为空")] | ||||||
|  |     public override string Icon { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 父ID | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "ParentId不能为空")] | ||||||
|  |     public override long ParentId { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 菜单类型 | ||||||
|  |     /// </summary> | ||||||
|  |     public override TargetTypeEnum TargetType { get; set; } = TargetTypeEnum.SELF; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 标题 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "Title不能为空")] | ||||||
|  |     public override string Title { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 特殊验证 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="validationContext"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     /// <exception cref="NotImplementedException"></exception> | ||||||
|  |     public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) | ||||||
|  |     { | ||||||
|  |         //如果菜单类型是菜单 | ||||||
|  |         if (TargetType == TargetTypeEnum.SELF) | ||||||
|  |         { | ||||||
|  |             if (string.IsNullOrEmpty(Component)) | ||||||
|  |                 yield return new ValidationResult("路径不能为空", new[] { nameof(Component) }); | ||||||
|  |         } | ||||||
|  |         //设置分类为菜单 | ||||||
|  |         Category = ResourceCategoryEnum.MENU; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 编辑菜单输入参数 | ||||||
|  | /// </summary> | ||||||
|  | public class MenuEditInput : MenuAddInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// ID | ||||||
|  |     /// </summary> | ||||||
|  |     [MinValue(1, ErrorMessage = "Id不能为空")] | ||||||
|  |     public override long Id { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 菜单树查询参数 | ||||||
|  | /// </summary> | ||||||
|  | public class MenuPageInput : BasePageInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 父ID | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "ParentId不能为空")] | ||||||
|  |     public long ParentId { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,58 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 菜单服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface IMenuService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加菜单 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">添加参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task AddAsync(MenuAddInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 删除菜单 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">删除菜单参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task DeleteAsync(params long[] input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 详情 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">id</param> | ||||||
|  |     /// <returns>详细信息</returns> | ||||||
|  |     Task<SysResource> DetailAsync(BaseIdInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 编辑菜单 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">菜单编辑参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task EditAsync(MenuEditInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据模块获取菜单树 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">菜单树查询参数</param> | ||||||
|  |     /// <returns>菜单树列表</returns> | ||||||
|  |     Task<List<SysResource>> TreeAsync(MenuPageInput input); | ||||||
|  | } | ||||||
| @@ -0,0 +1,168 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  | using Furion.FriendlyException; | ||||||
|  |  | ||||||
|  | using Mapster; | ||||||
|  |  | ||||||
|  | using SqlSugar; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// <inheritdoc cref="IMenuService"/> | ||||||
|  | /// </summary> | ||||||
|  | [Injection(Proxy = typeof(OperDispatchProxy))] | ||||||
|  | public class MenuService : DbRepository<SysResource>, IMenuService | ||||||
|  | { | ||||||
|  |     private readonly IRelationService _relationService; | ||||||
|  |     private readonly IResourceService _resourceService; | ||||||
|  |     private readonly IRoleService _roleService; | ||||||
|  |     /// <inheritdoc cref="IMenuService"/> | ||||||
|  |     public MenuService(IResourceService resourceService, IRelationService relationService, IRoleService roleService) | ||||||
|  |     { | ||||||
|  |         _roleService = roleService; | ||||||
|  |         _resourceService = resourceService; | ||||||
|  |         _relationService = relationService; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     [OperDesc("添加菜单")] | ||||||
|  |     public async Task AddAsync(MenuAddInput input) | ||||||
|  |     { | ||||||
|  |         await CheckInputAsync(input);//检查参数 | ||||||
|  |         var sysResource = input.Adapt<SysResource>();//实体转换 | ||||||
|  |  | ||||||
|  |         if (await InsertAsync(sysResource))//插入数据 | ||||||
|  |             _resourceService.RefreshCache(ResourceCategoryEnum.MENU);//刷新菜单缓存 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     [OperDesc("删除菜单")] | ||||||
|  |     public async Task DeleteAsync(params long[] input) | ||||||
|  |     { | ||||||
|  |         //获取所有ID | ||||||
|  |         var ids = input.ToList(); | ||||||
|  |         if (ids.Count > 0) | ||||||
|  |         { | ||||||
|  |             //获取所有菜单和按钮 | ||||||
|  |             var resourceList = await _resourceService.GetListByCategorysAsync(new List<ResourceCategoryEnum> { ResourceCategoryEnum.MENU, ResourceCategoryEnum.BUTTON }); | ||||||
|  |             //找到要删除的菜单 | ||||||
|  |             var sysResources = resourceList.Where(it => ids.Contains(it.Id)).ToList(); | ||||||
|  |             //查找内置菜单 | ||||||
|  |             var system = sysResources.Where(it => it.Code == ResourceConst.System).FirstOrDefault(); | ||||||
|  |             if (system != null) | ||||||
|  |                 throw Oops.Bah($"不可删除系统菜单:{system.Title}"); | ||||||
|  |             //需要删除的资源ID列表 | ||||||
|  |             var resourceIds = new List<long>(); | ||||||
|  |             //遍历菜单列表 | ||||||
|  |             sysResources.ForEach(it => | ||||||
|  |             { | ||||||
|  |                 //获取菜单所有子节点 | ||||||
|  |                 var child = _resourceService.GetChildListById(resourceList, it.Id, false); | ||||||
|  |                 //将子节点ID添加到删除资源ID列表 | ||||||
|  |                 resourceIds.AddRange(child.Select(it => it.Id).ToList()); | ||||||
|  |                 resourceIds.Add(it.Id);//添加到删除资源ID列表 | ||||||
|  |             }); | ||||||
|  |             ids.AddRange(resourceIds);//添加到删除ID列表 | ||||||
|  |                                       //事务 | ||||||
|  |             var result = await itenant.UseTranAsync(async () => | ||||||
|  |             { | ||||||
|  |                 await DeleteByIdsAsync(ids.Cast<object>().ToArray());//删除菜单和按钮 | ||||||
|  |                 await Context.Deleteable<SysRelation>()//关系表删除对应SYS_ROLE_HAS_RESOURCE | ||||||
|  |                  .Where(it => it.Category == CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE && resourceIds.Contains(SqlFunc.ToInt64(it.TargetId))).ExecuteCommandAsync(); | ||||||
|  |             }); | ||||||
|  |             if (result.IsSuccess)//如果成功了 | ||||||
|  |             { | ||||||
|  |                 _resourceService.RefreshCache(ResourceCategoryEnum.MENU);//资源表菜单刷新缓存 | ||||||
|  |                 _resourceService.RefreshCache(ResourceCategoryEnum.BUTTON);//资源表按钮刷新缓存 | ||||||
|  |                 _relationService.RefreshCache(CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE);//关系表刷新缓存 | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 //写日志 | ||||||
|  |                 throw Oops.Oh(result.ErrorMessage); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public async Task<SysResource> DetailAsync(BaseIdInput input) | ||||||
|  |     { | ||||||
|  |         var sysResources = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.MENU); | ||||||
|  |         var resource = sysResources.Where(it => it.Id == input.Id).FirstOrDefault(); | ||||||
|  |         return resource; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     [OperDesc("编辑菜单")] | ||||||
|  |     public async Task EditAsync(MenuEditInput input) | ||||||
|  |     { | ||||||
|  |         await CheckInputAsync(input);//检查参数 | ||||||
|  |         var sysResource = input.Adapt<SysResource>();//实体转换 | ||||||
|  |         if (await UpdateAsync(sysResource))//更新数据 | ||||||
|  |         { | ||||||
|  |             _resourceService.RefreshCache(ResourceCategoryEnum.MENU);//刷新菜单缓存 | ||||||
|  |             //需要更新资源权限,因为地址可能改变,页面权限需要更改 | ||||||
|  |             await _roleService.RefreshResourceAsync(input.Id); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public async Task<List<SysResource>> TreeAsync(MenuPageInput input) | ||||||
|  |     { | ||||||
|  |         //获取所有菜单 | ||||||
|  |         var sysResources = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.MENU); | ||||||
|  |         sysResources = sysResources | ||||||
|  |             .Where(it => it.ParentId == input.ParentId) | ||||||
|  |             .WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Title == input.SearchKey)//根据关键字查找 | ||||||
|  |             .ToList(); | ||||||
|  |         //构建菜单树 | ||||||
|  |         var tree = _resourceService.ResourceListToTree(sysResources, input.ParentId); | ||||||
|  |         return tree; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #region 方法 | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 检查输入参数 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="sysResource"></param> | ||||||
|  |     private async Task CheckInputAsync(SysResource sysResource) | ||||||
|  |     { | ||||||
|  |         //获取所有菜单列表 | ||||||
|  |         var menList = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.MENU); | ||||||
|  |         //判断是否有同级且同名的菜单 | ||||||
|  |         if (menList.Any(it => it.ParentId == sysResource.ParentId && it.Title == sysResource.Title && it.Id != sysResource.Id)) | ||||||
|  |             throw Oops.Bah($"存在重复的菜单名称:{sysResource.Title}"); | ||||||
|  |         if (sysResource.ParentId != 0) | ||||||
|  |         { | ||||||
|  |             //获取父级,判断父级ID正不正确 | ||||||
|  |             var parent = menList.Where(it => it.Id == sysResource.ParentId).FirstOrDefault(); | ||||||
|  |             if (parent != null) | ||||||
|  |             { | ||||||
|  |                 if (parent.Id == sysResource.Id) | ||||||
|  |                     throw Oops.Bah($"上级菜单不能选择自己"); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 throw Oops.Bah($"上级菜单不存在:{sysResource.ParentId}"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #endregion 方法 | ||||||
|  | } | ||||||
| @@ -0,0 +1,32 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 通知服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface INoticeService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 通知用户下线 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="userId">用户ID</param> | ||||||
|  |     /// <param name="verificatInfos">验证列表</param> | ||||||
|  |     /// <param name="message">消息内容</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task LogoutAsync(long userId, List<VerificatInfo> verificatInfos, string message); | ||||||
|  | } | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion; | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.SignalR; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// <inheritdoc cref="INoticeService"/> | ||||||
|  | /// </summary> | ||||||
|  | public class NoticeService : INoticeService | ||||||
|  | { | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task LogoutAsync(long userId, List<VerificatInfo> verificatInfos, string message) | ||||||
|  |     { | ||||||
|  |         //客户端ID列表 | ||||||
|  |         var clientIds = new List<string>(); | ||||||
|  |         //遍历token列表获取客户端ID列表 | ||||||
|  |         verificatInfos.ForEach(it => | ||||||
|  |         { | ||||||
|  |             clientIds.AddRange(it.ClientIds); | ||||||
|  |         }); | ||||||
|  |         //获取signalr实例 | ||||||
|  |         var signalr = App.GetService<IHubContext<SysHub, ISysHub>>(); | ||||||
|  |         //发送其他客户端登录消息 | ||||||
|  |         await signalr.Clients.Users(clientIds).Logout(message); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 操作日志分页输入 | ||||||
|  | /// </summary> | ||||||
|  | public class OperateLogPageInput : VisitLogPageInput | ||||||
|  | { | ||||||
|  | } | ||||||
|  | /// <summary> | ||||||
|  | /// 操作日志分页输入 | ||||||
|  | /// </summary> | ||||||
|  | public class OperateLogInput : VisitLogInput | ||||||
|  | { | ||||||
|  | } | ||||||
| @@ -0,0 +1,49 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 操作日志服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface IOperateLogService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据分类删除操作日志 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="category">分类名称</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task DeleteAsync(params string[] category); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 导出后台日志 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<MemoryStream> ExportFileAsync(List<SysOperateLog> input = null); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 导出后台日志 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<MemoryStream> ExportFileAsync(OperateLogInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 操作日志分页查询 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">查询参数</param> | ||||||
|  |     /// <returns>分页列表</returns> | ||||||
|  |     Task<SqlSugarPagedList<SysOperateLog>> PageAsync(OperateLogPageInput input); | ||||||
|  | } | ||||||
| @@ -0,0 +1,104 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using Mapster; | ||||||
|  |  | ||||||
|  | using MiniExcelLibs; | ||||||
|  |  | ||||||
|  | using SqlSugar; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// <inheritdoc cref="IOperateLogService"/> | ||||||
|  | /// </summary> | ||||||
|  | [Injection(Proxy = typeof(OperDispatchProxy))] | ||||||
|  | public class OperateLogService : DbRepository<SysOperateLog>, IOperateLogService | ||||||
|  | { | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     [OperDesc("删除操作日志")] | ||||||
|  |     public async Task DeleteAsync(params string[] category) | ||||||
|  |     { | ||||||
|  |         await AsDeleteable().Where(it => category.Contains(it.Category)).ExecuteCommandAsync(); | ||||||
|  |     } | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("导出操作日志", IsRecordPar = false)] | ||||||
|  |     public async Task<MemoryStream> ExportFileAsync(List<SysOperateLog> input = null) | ||||||
|  |     { | ||||||
|  |         input ??= await GetListAsync(); | ||||||
|  |  | ||||||
|  |         //总数据 | ||||||
|  |         Dictionary<string, object> sheets = new(); | ||||||
|  |         List<Dictionary<string, object>> devExports = new(); | ||||||
|  |         foreach (var devData in input) | ||||||
|  |         { | ||||||
|  |             #region sheet | ||||||
|  |             //变量页 | ||||||
|  |             var data = devData.GetType().GetPropertiesWithCache(); | ||||||
|  |             Dictionary<string, object> devExport = new(); | ||||||
|  |             foreach (var item in data) | ||||||
|  |             { | ||||||
|  |                 //描述 | ||||||
|  |                 var desc = TypeExtensions.FindDisplayAttribute(item); | ||||||
|  |                 //数据源增加 | ||||||
|  |                 devExport.Add(desc ?? item.Name, item.GetValue(devData)?.ToString()); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             devExports.Add(devExport); | ||||||
|  |  | ||||||
|  |             #endregion | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         sheets.Add("操作日志", devExports); | ||||||
|  |  | ||||||
|  |         var memoryStream = new MemoryStream(); | ||||||
|  |         await memoryStream.SaveAsAsync(sheets); | ||||||
|  |         return memoryStream; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("导出操作日志", IsRecordPar = false)] | ||||||
|  |     public async Task<MemoryStream> ExportFileAsync(OperateLogInput input) | ||||||
|  |     { | ||||||
|  |         var query = GetPage(input.Adapt<OperateLogPageInput>()); | ||||||
|  |         var data = await query.ToListAsync(); | ||||||
|  |         return await ExportFileAsync(data); | ||||||
|  |     } | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public async Task<SqlSugarPagedList<SysOperateLog>> PageAsync(OperateLogPageInput input) | ||||||
|  |     { | ||||||
|  |         var query = GetPage(input); | ||||||
|  |         var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页 | ||||||
|  |         return pageInfo; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private ISugarQueryable<SysOperateLog> GetPage(OperateLogPageInput input) | ||||||
|  |     { | ||||||
|  |         var query = Context.Queryable<SysOperateLog>() | ||||||
|  |                            .WhereIF(!string.IsNullOrEmpty(input.Account), it => it.OpAccount == input.Account)//根据账号查询 | ||||||
|  |                            .WhereIF(!string.IsNullOrEmpty(input.Category), it => it.Category == input.Category)//根据分类查询 | ||||||
|  |                            .WhereIF(!string.IsNullOrEmpty(input.ExeStatus), it => it.ExeStatus == input.ExeStatus)//根据结果查询 | ||||||
|  |                            .WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Name.Contains(input.SearchKey) || it.OpIp.Contains(input.SearchKey));//根据关键字查询 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         for (int i = 0; i < input.SortField.Count; i++) | ||||||
|  |         { | ||||||
|  |             query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}"); | ||||||
|  |         } | ||||||
|  |         query = query.OrderBy(it => it.Id, OrderByType.Desc);//排序 | ||||||
|  |         return query; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,99 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 关系服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface IRelationService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取关系表用户工作台 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="userId">用户ID</param> | ||||||
|  |     /// <returns>关系表数据</returns> | ||||||
|  |     Task<SysRelation> GetWorkbenchAsync(long userId); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据分类获取关系表信息 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="category">分类名称</param> | ||||||
|  |     /// <returns>关系表</returns> | ||||||
|  |     Task<List<SysRelation>> GetRelationByCategoryAsync(string category); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 通过对象ID和分类获取关系列表 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="objectId">对象ID</param> | ||||||
|  |     /// <param name="category">分类</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<SysRelation>> GetRelationListByObjectIdAndCategoryAsync(long objectId, string category); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 通过对象ID列表和分类获取关系列表 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="objectIds">对象ID</param> | ||||||
|  |     /// <param name="category">分类</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<SysRelation>> GetRelationListByObjectIdListAndCategoryAsync(List<long> objectIds, string category); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 通过目标ID和分类获取关系列表 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="targetId">目标ID</param> | ||||||
|  |     /// <param name="category">分类</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<SysRelation>> GetRelationListByTargetIdAndCategoryAsync(string targetId, string category); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 通过目标ID列表和分类获取关系列表 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="targetIds"></param> | ||||||
|  |     /// <param name="category"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<SysRelation>> GetRelationListByTargetIdListAndCategoryAsync(List<string> targetIds, string category); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 更新缓存 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="category">分类</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     void RefreshCache(string category); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 保存关系 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="category">分类</param> | ||||||
|  |     /// <param name="objectId">对象ID</param> | ||||||
|  |     /// <param name="targetId">目标ID</param> | ||||||
|  |     /// <param name="extJson">拓展信息</param> | ||||||
|  |     /// <param name="clear">是否清除老的数据</param> | ||||||
|  |     /// <param name="refreshCache">是否刷新缓存</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task SaveRelationAsync(string category, long objectId, string targetId, string extJson, bool clear, bool refreshCache = true); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 批量保存关系 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="category">分类</param> | ||||||
|  |     /// <param name="objectId">对象ID</param> | ||||||
|  |     /// <param name="targetIds">目标ID列表</param> | ||||||
|  |     /// <param name="extJsons">拓展信息列表</param> | ||||||
|  |     /// <param name="clear">是否清除老的数据</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task SaveRelationBatchAsync(string category, long objectId, List<string> targetIds, List<string> extJsons, bool clear); | ||||||
|  | } | ||||||
| @@ -0,0 +1,145 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.FriendlyException; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <inheritdoc cref="IRelationService"/> | ||||||
|  | public class RelationService : DbRepository<SysRelation>, IRelationService | ||||||
|  | { | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<SysRelation>> GetRelationByCategoryAsync(string category) | ||||||
|  |     { | ||||||
|  |         //先从Cache拿,需要获取新的对象,避免操作导致缓存中对象改变 | ||||||
|  |         var sysRelations = CacheStatic.Cache.Get<List<SysRelation>>(CacheConst.CACHE_SYSRELATION + category, true); | ||||||
|  |         if (sysRelations == null) | ||||||
|  |         { | ||||||
|  |             //cache没有就去数据库拿 | ||||||
|  |             sysRelations = await GetListAsync(it => it.Category == category); | ||||||
|  |             if (sysRelations.Count > 0) | ||||||
|  |             { | ||||||
|  |                 //插入Cache | ||||||
|  |                 CacheStatic.Cache.Set(CacheConst.CACHE_SYSRELATION + category, sysRelations, true); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return sysRelations; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<SysRelation>> GetRelationListByObjectIdAndCategoryAsync(long objectId, string category) | ||||||
|  |     { | ||||||
|  |         var sysRelations = await GetRelationByCategoryAsync(category); | ||||||
|  |         var result = sysRelations.Where(it => it.ObjectId == objectId).ToList();//获取关系集合 | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<SysRelation>> GetRelationListByObjectIdListAndCategoryAsync(List<long> objectIds, string category) | ||||||
|  |     { | ||||||
|  |         var sysRelations = await GetRelationByCategoryAsync(category); | ||||||
|  |         var result = sysRelations.Where(it => objectIds.Contains(it.ObjectId)).ToList();//获取关系集合 | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<SysRelation>> GetRelationListByTargetIdAndCategoryAsync(string targetId, string category) | ||||||
|  |     { | ||||||
|  |         var sysRelations = await GetRelationByCategoryAsync(category); | ||||||
|  |         var result = sysRelations.Where(it => it.TargetId == targetId).ToList();//获取关系集合 | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<SysRelation>> GetRelationListByTargetIdListAndCategoryAsync(List<string> targetIds, string category) | ||||||
|  |     { | ||||||
|  |         var sysRelations = await GetRelationByCategoryAsync(category); | ||||||
|  |         var result = sysRelations.Where(it => targetIds.Contains(it.TargetId)).ToList();//获取关系集合 | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<SysRelation> GetWorkbenchAsync(long userId) | ||||||
|  |     { | ||||||
|  |         var sysRelations = await GetRelationByCategoryAsync(CateGoryConst.Relation_SYS_USER_WORKBENCH_DATA); | ||||||
|  |         var result = sysRelations.FirstOrDefault(it => it.ObjectId == userId);//获取个人工作台 | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public void RefreshCache(string category) | ||||||
|  |     { | ||||||
|  |         CacheStatic.Cache.Remove(CacheConst.CACHE_SYSRELATION + category);//删除cache | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task SaveRelationAsync(string category, long objectId, string targetId, string extJson, bool clear, bool refreshCache = true) | ||||||
|  |     { | ||||||
|  |         var sysRelation = new SysRelation | ||||||
|  |         { | ||||||
|  |             ObjectId = objectId, | ||||||
|  |             TargetId = targetId, | ||||||
|  |             Category = category, | ||||||
|  |             ExtJson = extJson | ||||||
|  |         }; | ||||||
|  |         //事务 | ||||||
|  |         var result = await itenant.UseTranAsync(async () => | ||||||
|  |         { | ||||||
|  |             if (clear) | ||||||
|  |                 await DeleteAsync(it => it.ObjectId == objectId && it.Category == category);//删除老的 | ||||||
|  |             await InsertAsync(sysRelation);//添加新的 | ||||||
|  |         }); | ||||||
|  |         if (result.IsSuccess)//如果成功了 | ||||||
|  |         { | ||||||
|  |             if (refreshCache) | ||||||
|  |                 RefreshCache(category); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             //写日志 | ||||||
|  |             throw Oops.Oh(result.ErrorMessage); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task SaveRelationBatchAsync(string category, long objectId, List<string> targetIds, List<string> extJsons, bool clear) | ||||||
|  |     { | ||||||
|  |         var sysRelations = new List<SysRelation>();//要添加的列表 | ||||||
|  |         for (int i = 0; i < targetIds.Count; i++) | ||||||
|  |         { | ||||||
|  |             sysRelations.Add(new SysRelation | ||||||
|  |             { | ||||||
|  |                 ObjectId = objectId, | ||||||
|  |                 TargetId = targetIds[i], | ||||||
|  |                 Category = category, | ||||||
|  |                 ExtJson = extJsons?[i] | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var result = await itenant.UseTranAsync(async () => | ||||||
|  |         { | ||||||
|  |             if (clear) | ||||||
|  |                 await DeleteAsync(it => it.ObjectId == objectId && it.Category == category);//删除老的 | ||||||
|  |             await InsertRangeAsync(sysRelations);//添加新的 | ||||||
|  |         }); | ||||||
|  |         if (result.IsSuccess)//如果成功了 | ||||||
|  |         { | ||||||
|  |             RefreshCache(category); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             throw Oops.Oh(result.ErrorMessage); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,72 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 角色按钮资源 | ||||||
|  | /// </summary> | ||||||
|  | public class RoleGrantResourceButton | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 按钮id | ||||||
|  |     /// </summary> | ||||||
|  |     public long Id { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 标题 | ||||||
|  |     /// </summary> | ||||||
|  |     public string Title { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 授权菜单类 | ||||||
|  | /// </summary> | ||||||
|  | public class RoleGrantResourceMenu | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 菜单下按钮集合 | ||||||
|  |     /// </summary> | ||||||
|  |     public List<RoleGrantResourceButton> Button { get; set; } = new List<RoleGrantResourceButton>(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 菜单id | ||||||
|  |     /// </summary> | ||||||
|  |     public long Id { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 父id | ||||||
|  |     /// </summary> | ||||||
|  |     public long ParentId { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 父名称 | ||||||
|  |     /// </summary> | ||||||
|  |     public string ParentName { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 菜单名称 | ||||||
|  |     /// </summary> | ||||||
|  |     public string Title { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Blazor Server的组件路由内容 | ||||||
|  | /// </summary> | ||||||
|  | public class PermissionTreeSelector | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 路由名称 | ||||||
|  |     /// </summary> | ||||||
|  |     public string ApiRoute { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,105 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 资源服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface IResourceService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取所有的菜单和模块以及单页面列表,并按分类和排序码排序,不会形成树列表 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns>所有的菜单和模块以及单页面列表</returns> | ||||||
|  |     Task<List<SysResource>> GetaMenuAndSpaListAsync(); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取子资源 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="sysResources"></param> | ||||||
|  |     /// <param name="resId"></param> | ||||||
|  |     /// <param name="isContainOneself"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     List<SysResource> GetChildListById(List<SysResource> sysResources, long resId, bool isContainOneself = true); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取ID获取Code列表 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="ids">id列表</param> | ||||||
|  |     /// <param name="category">分类</param> | ||||||
|  |     /// <returns>Code列表</returns> | ||||||
|  |     Task<List<string>> GetCodeByIdsAsync(List<long> ids, ResourceCategoryEnum category); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据分类获取资源列表 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="category">分类名称</param> | ||||||
|  |     /// <returns>资源列表</returns> | ||||||
|  |     Task<List<SysResource>> GetListByCategoryAsync(ResourceCategoryEnum category); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 资源分类列表,如果是空的则获取全部资源 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="categorys">资源分类列表</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<SysResource>> GetListByCategorysAsync(List<ResourceCategoryEnum> categorys = null); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取资源所有下级 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="resourceList">资源列表</param> | ||||||
|  |     /// <param name="parentId">父ID</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     List<SysResource> GetResourceChilden(List<SysResource> resourceList, long parentId); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取上级 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     List<SysResource> GetResourceParent(List<SysResource> resourceList, long parentId); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取授权菜单 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<RoleGrantResourceMenu>> GetRoleGrantResourceMenusAsync(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 刷新缓存 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="category">分类名称</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     void RefreshCache(ResourceCategoryEnum category); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 构建菜单树形结构 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="resourceList">菜单列表</param> | ||||||
|  |     /// <param name="parentId">父ID</param> | ||||||
|  |     /// <returns>菜单形结构</returns> | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     List<SysResource> ResourceListToTree(List<SysResource> resourceList, long parentId = 0); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 多个树转列表 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="data"></param> | ||||||
|  |     List<SysResource> ResourceTreeToList(List<SysResource> data); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取PageTabItems | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="nav"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     List<PageTabItem> SameLevelMenuPasePageTab(List<SysResource> nav); | ||||||
|  | } | ||||||
| @@ -0,0 +1,288 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Mapster; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Tab表示类 | ||||||
|  | /// </summary> | ||||||
|  | /// <param name="Title">标题</param> | ||||||
|  | /// <param name="Href">跳转类型</param> | ||||||
|  | /// <param name="Icon">图标</param> | ||||||
|  | public record PageTabItem(string Title, string Href, string Icon); | ||||||
|  |  | ||||||
|  | /// <inheritdoc cref="IResourceService"/> | ||||||
|  | public class ResourceService : DbRepository<SysResource>, IResourceService | ||||||
|  | { | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<SysResource>> GetaMenuAndSpaListAsync() | ||||||
|  |     { | ||||||
|  |         //获取所有的菜单以及单页面 | ||||||
|  |         var sysResources = await GetListByCategorysAsync((List<ResourceCategoryEnum>)new() { ResourceCategoryEnum.MENU, ResourceCategoryEnum.SPA }); | ||||||
|  |         return sysResources?.OrderBy(it => it.Category).ThenBy(it => it.SortCode).ToList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public List<SysResource> GetChildListById(List<SysResource> sysResources, long resId, bool isContainOneself = true) | ||||||
|  |     { | ||||||
|  |         //查找下级 | ||||||
|  |         var childLsit = GetResourceChilden(sysResources, resId); | ||||||
|  |         if (isContainOneself)//如果包含自己 | ||||||
|  |         { | ||||||
|  |             //获取自己 | ||||||
|  |             var self = sysResources.Where(it => it.Id == resId).FirstOrDefault(); | ||||||
|  |             if (self != null) childLsit.Insert(0, self);//如果不为空就插到第一个 | ||||||
|  |         } | ||||||
|  |         return childLsit; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public async Task<List<string>> GetCodeByIdsAsync(List<long> ids, ResourceCategoryEnum category) | ||||||
|  |     { | ||||||
|  |         //根据分类获取所有 | ||||||
|  |         var sysResources = await GetListByCategoryAsync(category); | ||||||
|  |         return sysResources.Where(it => ids.Contains(it.Id)).Select(it => it.Code).ToList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public async Task<List<SysResource>> GetListByCategoryAsync(ResourceCategoryEnum category) | ||||||
|  |     { | ||||||
|  |         //先从Cache拿,需要获取新的对象,避免操作导致缓存中对象改变 | ||||||
|  |         var sysResources = CacheStatic.Cache.Get<List<SysResource>>(CacheConst.CACHE_SYSRESOURCE + category.ToString(), true); | ||||||
|  |         if (sysResources == null) | ||||||
|  |         { | ||||||
|  |             //cache没有就去数据库拿 | ||||||
|  |             sysResources = await GetListAsync(it => it.Category == category); | ||||||
|  |             if (sysResources.Count > 0) | ||||||
|  |             { | ||||||
|  |                 //插入Cache | ||||||
|  |                 CacheStatic.Cache.Set(CacheConst.CACHE_SYSRESOURCE + category.ToString(), sysResources, true); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return sysResources; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<SysResource>> GetListByCategorysAsync(List<ResourceCategoryEnum> categoryList = null) | ||||||
|  |     { | ||||||
|  |         //定义结果 | ||||||
|  |         var sysResources = new List<SysResource>(); | ||||||
|  |  | ||||||
|  |         //定义资源分类列表,如果是空的则获取全部资源 | ||||||
|  |         categoryList = categoryList != null ? categoryList | ||||||
|  |             : new List<ResourceCategoryEnum> { ResourceCategoryEnum.MENU, ResourceCategoryEnum.BUTTON, ResourceCategoryEnum.SPA }; | ||||||
|  |         //遍历列表 | ||||||
|  |         foreach (var category in categoryList) | ||||||
|  |         { | ||||||
|  |             //根据分类获取到资源列表 | ||||||
|  |             var data = await GetListByCategoryAsync(category); | ||||||
|  |             //添加到结果集 | ||||||
|  |             sysResources.AddRange(data); | ||||||
|  |         } | ||||||
|  |         return sysResources; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public List<SysResource> GetResourceChilden(List<SysResource> resourceList, long parentId) | ||||||
|  |     { | ||||||
|  |         //找下级资源ID列表 | ||||||
|  |         var resources = resourceList.Where(it => it.ParentId == parentId).ToList(); | ||||||
|  |         if (resources.Count > 0)//如果数量大于0 | ||||||
|  |         { | ||||||
|  |             var data = new List<SysResource>(); | ||||||
|  |             foreach (var item in resources)//遍历资源 | ||||||
|  |             { | ||||||
|  |                 var res = GetResourceChilden(resourceList, item.Id); | ||||||
|  |                 data.AddRange(res);//添加子节点; | ||||||
|  |                 data.Add(item);//添加到列表 | ||||||
|  |             } | ||||||
|  |             return data;//返回结果 | ||||||
|  |         } | ||||||
|  |         return new List<SysResource>(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public List<SysResource> GetResourceParent(List<SysResource> resourceList, long parentId) | ||||||
|  |     { | ||||||
|  |         //找上级资源ID列表 | ||||||
|  |         var resources = resourceList.Where(it => it.Id == parentId).FirstOrDefault(); | ||||||
|  |         if (resources != null)//如果数量大于0 | ||||||
|  |         { | ||||||
|  |             var data = new List<SysResource>(); | ||||||
|  |             var parents = GetResourceParent(resourceList, resources.ParentId); | ||||||
|  |             data.AddRange(parents);//添加子节点; | ||||||
|  |             data.Add(resources);//添加到列表 | ||||||
|  |             return data;//返回结果 | ||||||
|  |         } | ||||||
|  |         return new List<SysResource>(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<RoleGrantResourceMenu>> GetRoleGrantResourceMenusAsync() | ||||||
|  |     { | ||||||
|  |         var roleGrantResourceMenus = new List<RoleGrantResourceMenu>();//定义结果 | ||||||
|  |         List<SysResource> allMenuList = (await GetListByCategoryAsync(ResourceCategoryEnum.MENU));//获取所有菜单列表 | ||||||
|  |         List<SysResource> allButtonList = await GetListByCategoryAsync(ResourceCategoryEnum.BUTTON);//获取所有按钮列表 | ||||||
|  |         var parentMenuList = allMenuList.Where(it => it.ParentId == 0).ToList();//获取一级目录 | ||||||
|  |  | ||||||
|  |         //遍历一级目录 | ||||||
|  |         foreach (var parent in parentMenuList) | ||||||
|  |         { | ||||||
|  |             //如果是目录则去遍历下级 | ||||||
|  |             if (parent.TargetType == TargetTypeEnum.None) | ||||||
|  |             { | ||||||
|  |                 //获取所有下级菜单 | ||||||
|  |                 var menuList = GetChildListById(allMenuList, parent.Id, false); | ||||||
|  |  | ||||||
|  |                 //遍历下级菜单 | ||||||
|  |                 foreach (var menu in menuList) | ||||||
|  |                 { | ||||||
|  |                     //如果菜单类型是菜单 | ||||||
|  |                     if (menu.TargetType == TargetTypeEnum.SELF) | ||||||
|  |                     { | ||||||
|  |                         //获取菜单下按钮集合并转换成对应实体 | ||||||
|  |                         var buttonList = allButtonList.Where(it => it.ParentId == menu.Id).ToList(); | ||||||
|  |                         var buttons = buttonList.Adapt<List<RoleGrantResourceButton>>(); | ||||||
|  |                         roleGrantResourceMenus.Add(new() | ||||||
|  |                         { | ||||||
|  |                             Id = menu.Id, | ||||||
|  |                             ParentId = parent.Id, | ||||||
|  |                             ParentName = parent.Title, | ||||||
|  |                             Title = GetRoleGrantResourceMenuTitle(parentMenuList, menu),//菜单名称需要特殊处理因为有二级菜单 | ||||||
|  |                             Button = buttons | ||||||
|  |                         }); | ||||||
|  |                     } | ||||||
|  |                     else if (menu.TargetType == TargetTypeEnum.BLANK || menu.TargetType == TargetTypeEnum.CALLBACK)//如果是内链或者外链 | ||||||
|  |                     { | ||||||
|  |                         //直接加到资源列表 | ||||||
|  |                         roleGrantResourceMenus.Add(new() | ||||||
|  |                         { | ||||||
|  |                             Id = menu.Id, | ||||||
|  |                             ParentId = parent.Id, | ||||||
|  |                             ParentName = parent.Title, | ||||||
|  |                             Title = menu.Title, | ||||||
|  |                         }); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 //否则就将自己加到一级目录里面 | ||||||
|  |                 roleGrantResourceMenus.Add(new() | ||||||
|  |                 { | ||||||
|  |                     Id = parent.Id, | ||||||
|  |                     ParentId = parent.Id, | ||||||
|  |                     ParentName = parent.Title, | ||||||
|  |                     Title = parent.Title, | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return roleGrantResourceMenus; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public void RefreshCache(ResourceCategoryEnum category) | ||||||
|  |     { | ||||||
|  |         //如果分类是空的 | ||||||
|  |         if (category == ResourceCategoryEnum.None) | ||||||
|  |         { | ||||||
|  |             //删除全部key | ||||||
|  |             CacheStatic.Cache.Remove(CacheConst.CACHE_SYSRESOURCE + ResourceCategoryEnum.SPA.ToString()); | ||||||
|  |             CacheStatic.Cache.Remove(CacheConst.CACHE_SYSRESOURCE + ResourceCategoryEnum.BUTTON.ToString()); | ||||||
|  |             CacheStatic.Cache.Remove(CacheConst.CACHE_SYSRESOURCE + ResourceCategoryEnum.MENU.ToString()); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             //否则只删除一个Key | ||||||
|  |             CacheStatic.Cache.Remove(CacheConst.CACHE_SYSRESOURCE + category.ToString()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public List<SysResource> ResourceListToTree(List<SysResource> resourceList, long parentId = 0) | ||||||
|  |     { | ||||||
|  |         //找下级资源ID列表 | ||||||
|  |         var resources = resourceList | ||||||
|  |            .Where(it => it.ParentId == parentId).OrderBy(it => it.SortCode).ToList(); | ||||||
|  |         if (resources.Count > 0)//如果数量大于0 | ||||||
|  |         { | ||||||
|  |             var data = new List<SysResource>(); | ||||||
|  |             foreach (var item in resources)//遍历资源 | ||||||
|  |             { | ||||||
|  |                 var children = ResourceListToTree(resourceList, item.Id);//添加子节点 | ||||||
|  |                 item.Children = children.Count > 0 ? children : null; | ||||||
|  |                 data.Add(item);//添加到列表 | ||||||
|  |             } | ||||||
|  |             return data;//返回结果 | ||||||
|  |         } | ||||||
|  |         return new List<SysResource>(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public List<SysResource> ResourceTreeToList(List<SysResource> data) | ||||||
|  |     { | ||||||
|  |         List<SysResource> list = new(); | ||||||
|  |         foreach (var item in data) | ||||||
|  |         { | ||||||
|  |             list.Add(item); | ||||||
|  |             if (item.Children != null && item.Children.Count > 0) | ||||||
|  |             { | ||||||
|  |                 list.AddRange(ResourceTreeToList(item.Children)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return list; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public List<PageTabItem> SameLevelMenuPasePageTab(List<SysResource> nav) | ||||||
|  |     { | ||||||
|  |         List<PageTabItem> pageTabItems = new(); | ||||||
|  |         if (nav == null) return pageTabItems; | ||||||
|  |         foreach (var item in nav) | ||||||
|  |         { | ||||||
|  |             if ((item.Category == ResourceCategoryEnum.MENU || item.Category == ResourceCategoryEnum.SPA) && item.TargetType == TargetTypeEnum.SELF) | ||||||
|  |             { | ||||||
|  |                 if (item.Icon == null) | ||||||
|  |                     pageTabItems.Add(new PageTabItem(item.Title, item.Component, "")); | ||||||
|  |                 else | ||||||
|  |                     pageTabItems.Add(new PageTabItem(item.Title, item.Component, item.Icon)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return pageTabItems; | ||||||
|  |     } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取授权菜单类菜单名称 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="menuList">菜单列表</param> | ||||||
|  |     /// <param name="menu">当前菜单</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     private string GetRoleGrantResourceMenuTitle(List<SysResource> menuList, SysResource menu) | ||||||
|  |     { | ||||||
|  |         //查找菜单上级 | ||||||
|  |         var parentList = GetResourceParent(menuList, menu.ParentId); | ||||||
|  |         //如果有父级菜单 | ||||||
|  |         if (parentList.Count > 0) | ||||||
|  |         { | ||||||
|  |             var titles = parentList.Select(it => it.Title).ToList();//提取出父级的name | ||||||
|  |             var title = string.Join("- ", titles) + $"-{menu.Title}";//根据-分割,转换成字符串并在最后加上菜单的title | ||||||
|  |             return title; | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             return menu.Title;//原路返回 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,84 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 角色授权资源参数 | ||||||
|  | /// </summary> | ||||||
|  | public class GrantResourceInput : RoleOwnResourceOutput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 授权资源信息 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "授权资源信息表不能为空")] | ||||||
|  |     public override List<RelationRoleResuorce> GrantInfoList { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 角色Id | ||||||
|  |     /// </summary> | ||||||
|  |     [MinValue(1, ErrorMessage = "Id不能为空")] | ||||||
|  |     public override long Id { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 角色授权用户参数 | ||||||
|  | /// </summary> | ||||||
|  | public class GrantUserInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 授权权限信息 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "GrantInfoList不能为空")] | ||||||
|  |     public List<long> GrantInfoList { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Id | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "Id不能为空")] | ||||||
|  |     public long? Id { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 角色添加参数 | ||||||
|  | /// </summary> | ||||||
|  | public class RoleAddInput : SysRole | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 名称 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "Name不能为空")] | ||||||
|  |     public override string Name { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 角色编辑参数 | ||||||
|  | /// </summary> | ||||||
|  | public class RoleEditInput : RoleAddInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Id | ||||||
|  |     /// </summary> | ||||||
|  |     [MinValue(1, ErrorMessage = "Id不能为空")] | ||||||
|  |     public override long Id { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 角色查询参数 | ||||||
|  | /// </summary> | ||||||
|  | public class RolePageInput : BasePageInput | ||||||
|  | { | ||||||
|  | } | ||||||
| @@ -0,0 +1,107 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 角色服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface IRoleService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加角色 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">添加参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task AddAsync(RoleAddInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 删除角色 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">删除参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task DeleteAsync(params long[] input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 编辑角色 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">编辑角色</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task EditAsync(RoleEditInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据用户ID获取用户角色Id集合 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="userId">用户ID</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<long>> GetRoleIdListByUserIdAsync(long userId); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据用户ID获取用户角色集合 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="userId">用户ID</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<SysRole>> GetRoleListByUserIdAsync(long userId); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 给角色授权资源 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">授权参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task GrantResourceAsync(GrantResourceInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 给角色授权用户 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">授权信息</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task GrantUserAsync(GrantUserInput input); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 角色拥有资源 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">角色id</param> | ||||||
|  |     /// <returns>角色拥有资源信息</returns> | ||||||
|  |     Task<RoleOwnResourceOutput> OwnResourceAsync(long input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取角色下的用户 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">角色ID</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<long>> OwnUserAsync(long input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 分页查询角色 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">查询参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<SqlSugarPagedList<SysRole>> PageAsync(RolePageInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 刷新缓存 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     void RefreshCache(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 角色刷新资源 | ||||||
|  |     /// </summary> | ||||||
|  |     Task RefreshResourceAsync(long? menuId = null); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 角色选择器 | ||||||
|  |     /// </summary> | ||||||
|  |     Task<List<SysRole>> RoleSelectorAsync(string searchKey = null); | ||||||
|  | } | ||||||
| @@ -0,0 +1,405 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  | using Furion.EventBus; | ||||||
|  | using Furion.FriendlyException; | ||||||
|  |  | ||||||
|  | using Mapster; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | using Yitter.IdGenerator; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application | ||||||
|  | { | ||||||
|  |     /// <inheritdoc cref="IRoleService"/> | ||||||
|  |     [Injection(Proxy = typeof(OperDispatchProxy))] | ||||||
|  |     public class RoleService : DbRepository<SysRole>, IRoleService | ||||||
|  |     { | ||||||
|  |         private readonly IEventPublisher _eventPublisher; | ||||||
|  |         private readonly IRelationService _relationService; | ||||||
|  |         private readonly IResourceService _resourceService; | ||||||
|  |  | ||||||
|  |         /// <inheritdoc cref="IRoleService"/> | ||||||
|  |         public RoleService( | ||||||
|  |                            IRelationService relationService, | ||||||
|  |                            IResourceService resourceService, | ||||||
|  |                            IEventPublisher eventPublisher) | ||||||
|  |         { | ||||||
|  |             _relationService = relationService; | ||||||
|  |             _resourceService = resourceService; | ||||||
|  |             _eventPublisher = eventPublisher; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         [OperDesc("添加角色")] | ||||||
|  |         public async Task AddAsync(RoleAddInput input) | ||||||
|  |         { | ||||||
|  |             await CheckInput(input);//检查参数 | ||||||
|  |             var sysRole = input.Adapt<SysRole>();//实体转换 | ||||||
|  |             sysRole.Code = YitIdHelper.NextId().ToString();//赋值Code | ||||||
|  |             if (await InsertAsync(sysRole))//插入数据 | ||||||
|  |                 RefreshCache();//刷新缓存 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         [OperDesc("删除角色")] | ||||||
|  |         public async Task DeleteAsync(params long[] input) | ||||||
|  |         { | ||||||
|  |             //获取所有ID | ||||||
|  |             var ids = input.ToList(); | ||||||
|  |             if (ids.Count > 0) | ||||||
|  |             { | ||||||
|  |                 var sysRoles = await GetListAsync();//获取所有角色 | ||||||
|  |                 var hasSuperAdmin = sysRoles.Any(it => it.Code == RoleConst.SuperAdmin && ids.Contains(it.Id));//判断是否有超级管理员 | ||||||
|  |                 if (hasSuperAdmin) throw Oops.Bah($"不可删除系统内置超管角色"); | ||||||
|  |  | ||||||
|  |                 //数据库是string所以这里转下 | ||||||
|  |                 var targetIds = ids.Select(it => it.ToString()).ToList(); | ||||||
|  |                 //定义删除的关系 | ||||||
|  |                 var delRelations = new List<string> { CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE, CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION }; | ||||||
|  |                 //事务 | ||||||
|  |                 var result = await itenant.UseTranAsync(async () => | ||||||
|  |                 { | ||||||
|  |                     await DeleteByIdsAsync(ids.Cast<object>().ToArray());//删除按钮 | ||||||
|  |  | ||||||
|  |                     //删除关系表角色与资源关系,角色与权限关系 | ||||||
|  |                     await Context.Deleteable<SysRelation>().Where(it => ids.Contains(it.ObjectId) && delRelations.Contains(it.Category)).ExecuteCommandAsync(); | ||||||
|  |                     //删除关系表角色与用户关系 | ||||||
|  |                     await Context.Deleteable<SysRelation>().Where(it => targetIds.Contains(it.TargetId) && it.Category == CateGoryConst.Relation_SYS_USER_HAS_ROLE).ExecuteCommandAsync(); | ||||||
|  |  | ||||||
|  |                 }); | ||||||
|  |                 if (result.IsSuccess)//如果成功了 | ||||||
|  |                 { | ||||||
|  |                     RefreshCache();//刷新缓存 | ||||||
|  |                     _relationService.RefreshCache(CateGoryConst.Relation_SYS_USER_HAS_ROLE);//关系表刷新SYS_USER_HAS_ROLE缓存 | ||||||
|  |                     _relationService.RefreshCache(CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE);//关系表刷新SYS_ROLE_HAS_RESOURCE缓存 | ||||||
|  |                     _relationService.RefreshCache(CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION);//关系表刷新SYS_ROLE_HAS_PERMISSION缓存 | ||||||
|  |                     await _eventPublisher.PublishAsync(EventSubscriberConst.ClearUserCache, ids);//清除角色下用户缓存 | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     //写日志 | ||||||
|  |                     throw Oops.Oh(result.ErrorMessage); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         [OperDesc("编辑角色")] | ||||||
|  |         public async Task EditAsync(RoleEditInput input) | ||||||
|  |         { | ||||||
|  |             //判断是否超管 | ||||||
|  |             if (input.Code == RoleConst.SuperAdmin) | ||||||
|  |                 throw Oops.Bah($"不可编辑超管角色"); | ||||||
|  |             await CheckInput(input);//检查参数 | ||||||
|  |             var role = await GetFirstAsync(it => it.Id == input.Id);//获取角色 | ||||||
|  |             if (role != null) | ||||||
|  |             { | ||||||
|  |                 var permissions = new List<SysRelation>(); | ||||||
|  |  | ||||||
|  |                 var sysRole = input.Adapt<SysRole>();//实体转换 | ||||||
|  |                                                      //事务 | ||||||
|  |                 var result = await itenant.UseTranAsync(async () => | ||||||
|  |                 { | ||||||
|  |                     await UpdateAsync(sysRole);//更新角色 | ||||||
|  |                     if (permissions.Any())//如果有授权权限就更新 | ||||||
|  |                         await Context.Updateable(permissions).ExecuteCommandAsync(); | ||||||
|  |                 }); | ||||||
|  |                 if (result.IsSuccess)//如果成功了 | ||||||
|  |                 { | ||||||
|  |                     RefreshCache();//刷新缓存 | ||||||
|  |                     if (permissions.Any())//如果有授权 | ||||||
|  |                         _relationService.RefreshCache(CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION);//关系表刷新SYS_ROLE_HAS_PERMISSION缓存 | ||||||
|  |                     await _eventPublisher.PublishAsync(EventSubscriberConst.ClearUserCache, new List<long> { input.Id });//清除角色下用户缓存 | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     //写日志 | ||||||
|  |                     throw Oops.Oh(result.ErrorMessage); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 获取所有角色 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns></returns> | ||||||
|  |         public override async Task<List<SysRole>> GetListAsync() | ||||||
|  |         { | ||||||
|  |             //先从Cache拿,需要获取新的对象,避免操作导致缓存中对象改变 | ||||||
|  |             var sysRoles = CacheStatic.Cache.Get<List<SysRole>>(CacheConst.CACHE_SYSROLE, true); | ||||||
|  |             if (sysRoles == null) | ||||||
|  |             { | ||||||
|  |                 //cache没有就去数据库拿 | ||||||
|  |                 sysRoles = await base.GetListAsync(); | ||||||
|  |                 if (sysRoles.Count > 0) | ||||||
|  |                 { | ||||||
|  |                     //插入Cache | ||||||
|  |                     CacheStatic.Cache.Set(CacheConst.CACHE_SYSROLE, sysRoles, true); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return sysRoles; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         public async Task<List<long>> GetRoleIdListByUserIdAsync(long userId) | ||||||
|  |         { | ||||||
|  |             List<SysRole> cods = new();//角色代码集合 | ||||||
|  |             var roleList = await _relationService.GetRelationListByObjectIdAndCategoryAsync(userId, CateGoryConst.Relation_SYS_USER_HAS_ROLE);//根据用户ID获取角色ID | ||||||
|  |             var roleIdList = roleList.Select(x => x.TargetId.ToLong()).ToList();//角色ID列表 | ||||||
|  |             return roleIdList; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         public async Task<List<SysRole>> GetRoleListByUserIdAsync(long userId) | ||||||
|  |         { | ||||||
|  |             List<SysRole> cods = new();//角色代码集合 | ||||||
|  |             var roleList = await _relationService.GetRelationListByObjectIdAndCategoryAsync(userId, CateGoryConst.Relation_SYS_USER_HAS_ROLE);//根据用户ID获取角色ID | ||||||
|  |             var roleIdList = roleList.Select(x => x.TargetId.ToLong()).ToList();//角色ID列表 | ||||||
|  |             if (roleIdList.Count > 0) | ||||||
|  |             { | ||||||
|  |                 cods = await GetListAsync(it => roleIdList.Contains(it.Id)); | ||||||
|  |             } | ||||||
|  |             return cods; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         [OperDesc("角色授权")] | ||||||
|  |         public async Task GrantResourceAsync(GrantResourceInput input) | ||||||
|  |         { | ||||||
|  |             var menuIds = input.GrantInfoList.Select(it => it.MenuId).ToList();//菜单ID | ||||||
|  |             var extJsons = input.GrantInfoList.Select(it => it.ToJsonString()).ToList();//拓展信息 | ||||||
|  |             var relationRoles = new List<SysRelation>();//要添加的角色资源和授权关系表 | ||||||
|  |             var sysRole = (await GetListAsync()).Where(it => it.Id == input.Id).FirstOrDefault();//获取角色 | ||||||
|  |             if (sysRole != null) | ||||||
|  |             { | ||||||
|  |                 #region 角色资源处理 | ||||||
|  |  | ||||||
|  |                 //遍历角色列表 | ||||||
|  |                 for (int i = 0; i < menuIds.Count; i++) | ||||||
|  |                 { | ||||||
|  |                     //将角色资源添加到列表 | ||||||
|  |                     relationRoles.Add(new SysRelation | ||||||
|  |                     { | ||||||
|  |                         ObjectId = sysRole.Id, | ||||||
|  |                         TargetId = menuIds[i].ToString(), | ||||||
|  |                         Category = CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE, | ||||||
|  |                         ExtJson = extJsons?[i] | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 #endregion 角色资源处理 | ||||||
|  |  | ||||||
|  |                 #region 角色权限处理 | ||||||
|  |  | ||||||
|  |                 var relationRolePer = new List<SysRelation>();//要添加的角色有哪些权限列表 | ||||||
|  |  | ||||||
|  |                 //获取菜单信息 | ||||||
|  |                 var menus = await GetMenuByMenuIds(menuIds); | ||||||
|  |                 if (menus.Count > 0) | ||||||
|  |                 { | ||||||
|  |                     //获取权限授权树 | ||||||
|  |                     var permissions = PermissionUtil.PermissionTreeSelector(menus.Select(it => it.Component).ToList()); | ||||||
|  |                     permissions.ForEach(it => | ||||||
|  |                     { | ||||||
|  |                         //新建角色权限关系 | ||||||
|  |                         relationRolePer.Add(new SysRelation | ||||||
|  |                         { | ||||||
|  |                             ObjectId = sysRole.Id, | ||||||
|  |                             TargetId = it.ApiRoute, | ||||||
|  |                             Category = CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION, | ||||||
|  |                             ExtJson = new RelationRolePermission | ||||||
|  |                             { | ||||||
|  |                                 ApiUrl = it.ApiRoute, | ||||||
|  |                             }.ToJsonString() | ||||||
|  |                         }); | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |                 relationRoles.AddRange(relationRolePer);//合并列表 | ||||||
|  |  | ||||||
|  |                 #endregion 角色权限处理 | ||||||
|  |  | ||||||
|  |                 #region 保存数据库 | ||||||
|  |  | ||||||
|  |                 //事务 | ||||||
|  |                 var result = await itenant.UseTranAsync(async () => | ||||||
|  |                 { | ||||||
|  |                     //删除老的 | ||||||
|  |                     await Context.Deleteable<SysRelation>().Where(it => it.ObjectId == sysRole.Id | ||||||
|  |                     && | ||||||
|  |                     (it.Category == CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION | ||||||
|  |                     || it.Category == CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE) | ||||||
|  |                     ) | ||||||
|  |                     .ExecuteCommandAsync(); | ||||||
|  |                     await Context.Insertable(relationRoles).ExecuteCommandAsync(); | ||||||
|  |                 }); | ||||||
|  |                 if (result.IsSuccess)//如果成功了 | ||||||
|  |                 { | ||||||
|  |                     _relationService.RefreshCache(CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE);//刷新关系缓存 | ||||||
|  |                     _relationService.RefreshCache(CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION);//刷新关系缓存 | ||||||
|  |                     await _eventPublisher.PublishAsync(EventSubscriberConst.ClearUserCache, new List<long> { input.Id });//发送事件清除角色下用户缓存 | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     //写日志 | ||||||
|  |                     throw Oops.Oh(result.ErrorMessage); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 #endregion 保存数据库 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         [OperDesc("用户授权")] | ||||||
|  |         public async Task GrantUserAsync(GrantUserInput input) | ||||||
|  |         { | ||||||
|  |             var sysRelations = new List<SysRelation>();//关系列表 | ||||||
|  |  | ||||||
|  |             //遍历用户ID | ||||||
|  |             input.GrantInfoList.ForEach(it => | ||||||
|  |             { | ||||||
|  |                 sysRelations.Add(new SysRelation | ||||||
|  |                 { | ||||||
|  |                     ObjectId = it, | ||||||
|  |                     TargetId = input.Id.ToString(), | ||||||
|  |                     Category = CateGoryConst.Relation_SYS_USER_HAS_ROLE | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             //事务 | ||||||
|  |             var result = await itenant.UseTranAsync(async () => | ||||||
|  |             { | ||||||
|  |                 //删除老的 | ||||||
|  |                 await Context.Deleteable<SysRelation>().Where(it => it.TargetId == input.Id.ToString() && it.Category == CateGoryConst.Relation_SYS_USER_HAS_ROLE).ExecuteCommandAsync(); | ||||||
|  |                 await Context.Insertable(sysRelations).ExecuteCommandAsync();//添加新的 | ||||||
|  |  | ||||||
|  |             }); | ||||||
|  |             if (result.IsSuccess)//如果成功了 | ||||||
|  |             { | ||||||
|  |                 _relationService.RefreshCache(CateGoryConst.Relation_SYS_USER_HAS_ROLE);//刷新关系表SYS_USER_HAS_ROLE缓存 | ||||||
|  |                 await _eventPublisher.PublishAsync(EventSubscriberConst.ClearUserCache, new List<long> { input.Id.Value });//清除角色下用户缓存 | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 //写日志 | ||||||
|  |                 throw Oops.Oh(result.ErrorMessage); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public async Task<RoleOwnResourceOutput> OwnResourceAsync(long input) | ||||||
|  |         { | ||||||
|  |             RoleOwnResourceOutput roleOwnResource = new() { Id = input };//定义结果集 | ||||||
|  |             List<RelationRoleResuorce> GrantInfoList = new();//已授权信息集合 | ||||||
|  |                                                              //获取关系列表 | ||||||
|  |             var relations = await _relationService.GetRelationListByObjectIdAndCategoryAsync(input, CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE); | ||||||
|  |             //遍历关系表 | ||||||
|  |             relations.ForEach(it => | ||||||
|  |             { | ||||||
|  |                 //将扩展信息转为实体 | ||||||
|  |                 var relationRole = it.ExtJson.ToJsonWithT<RelationRoleResuorce>(); | ||||||
|  |                 GrantInfoList.Add(relationRole);//添加到已授权信息 | ||||||
|  |             }); | ||||||
|  |             roleOwnResource.GrantInfoList = GrantInfoList;//赋值已授权信息 | ||||||
|  |             return roleOwnResource; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public async Task<List<long>> OwnUserAsync(long input) | ||||||
|  |         { | ||||||
|  |             //获取关系列表 | ||||||
|  |             var relations = await _relationService.GetRelationListByTargetIdAndCategoryAsync(input.ToString(), CateGoryConst.Relation_SYS_USER_HAS_ROLE); | ||||||
|  |             return relations.Select(it => it.ObjectId).ToList(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         public async Task<SqlSugarPagedList<SysRole>> PageAsync(RolePageInput input) | ||||||
|  |         { | ||||||
|  |             var query = Context.Queryable<SysRole>() | ||||||
|  |                              .WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Name.Contains(input.SearchKey));//根据关键字查询 | ||||||
|  |             for (int i = 0; i < input.SortField.Count; i++) | ||||||
|  |             { | ||||||
|  |                 query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}"); | ||||||
|  |             } | ||||||
|  |             query = query.OrderBy(it => it.SortCode);//排序 | ||||||
|  |  | ||||||
|  |             var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页 | ||||||
|  |             return pageInfo; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public void RefreshCache() | ||||||
|  |         { | ||||||
|  |             CacheStatic.Cache.Remove(CacheConst.CACHE_SYSROLE);//删除KEY | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public async Task RefreshResourceAsync(long? menuId = null) | ||||||
|  |         { | ||||||
|  |             var data = await GetListAsync(); | ||||||
|  |             foreach (var item in data) | ||||||
|  |             { | ||||||
|  |                 var r1 = await OwnResourceAsync(item.Id); | ||||||
|  |                 if (menuId == null || r1.GrantInfoList.Any(a => a.MenuId == menuId)) | ||||||
|  |                 { | ||||||
|  |                     await GrantResourceAsync(new GrantResourceInput() { Id = item.Id, GrantInfoList = r1.GrantInfoList }); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public async Task<List<SysRole>> RoleSelectorAsync(string searchKey = null) | ||||||
|  |         { | ||||||
|  |             var result = await Context.Queryable<SysRole>() | ||||||
|  |                              .WhereIF(!string.IsNullOrEmpty(searchKey), it => it.Name.Contains(searchKey))//根据关键字查询 | ||||||
|  |                              .ToListAsync(); | ||||||
|  |             return result; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         #region 方法 | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 检查输入参数 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="sysRole"></param> | ||||||
|  |         private async Task CheckInput(SysRole sysRole) | ||||||
|  |         { | ||||||
|  |             var sysRoles = await GetListAsync();//获取所有 | ||||||
|  |             var repeatName = sysRoles.Any(it => it.Name == sysRole.Name && it.Id != sysRole.Id);//是否有重复角色名称 | ||||||
|  |             if (repeatName)//如果有 | ||||||
|  |             { | ||||||
|  |                 throw Oops.Bah($"存在重复的角色:{sysRole.Name}"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 根据菜单ID获取菜单 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="menuIds"></param> | ||||||
|  |         /// <returns></returns> | ||||||
|  |         private async Task<List<SysResource>> GetMenuByMenuIds(List<long> menuIds) | ||||||
|  |         { | ||||||
|  |             //获取所有菜单 | ||||||
|  |             var menuList = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.MENU); | ||||||
|  |             //获取菜单信息 | ||||||
|  |             var menus = menuList.Where(it => menuIds.Contains(it.Id)).ToList(); | ||||||
|  |  | ||||||
|  |             return menus; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         #endregion 方法 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,58 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using System.ComponentModel; | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 会话分页查询 | ||||||
|  | /// </summary> | ||||||
|  | public class SessionPageInput : BasePageInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 账号 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("账号")] | ||||||
|  |     public string Account { get; set; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 最新登录IP | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("最新登录IP")] | ||||||
|  |     public string LatestLoginIp { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 姓名 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("姓名")] | ||||||
|  |     public string Name { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 退出参数 | ||||||
|  | /// </summary> | ||||||
|  | public class ExitVerificatInput : BaseIdInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 验证ID列表 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "VerificatIds不能为空")] | ||||||
|  |     public List<long> VerificatIds { get; set; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户Id | ||||||
|  |     /// </summary> | ||||||
|  |     [MinValue(1, ErrorMessage = "Id不能为空")] | ||||||
|  |     public override long Id { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,64 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using System.ComponentModel; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 会话输出 | ||||||
|  | /// </summary> | ||||||
|  | public class SessionOutput : PrimaryKeyEntity | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 账号 | ||||||
|  |     ///</summary> | ||||||
|  |     [Description("账号")] | ||||||
|  |     [DataTable(Order = 1, IsShow = true, Sortable = true)] | ||||||
|  |     public virtual string Account { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 最新登录ip | ||||||
|  |     ///</summary> | ||||||
|  |     [Description("最新登录ip")] | ||||||
|  |     [DataTable(Order = 3, IsShow = true, Sortable = true)] | ||||||
|  |     public string LatestLoginIp { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 最新登录时间 | ||||||
|  |     ///</summary> | ||||||
|  |     [Description("最新登录时间")] | ||||||
|  |     [DataTable(Order = 4, IsShow = true, Sortable = true)] | ||||||
|  |     public DateTime? LatestLoginTime { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 在线状态 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("在线状态")] | ||||||
|  |     [DataTable(Order = 2, IsShow = true, Sortable = true)] | ||||||
|  |     public bool OnlineStatus { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 令牌数量 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("令牌数量")] | ||||||
|  |     [DataTable(Order = 5, IsShow = true, Sortable = true)] | ||||||
|  |     public int VerificatCount { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 令牌信息集合 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("令牌列表")] | ||||||
|  |     public List<VerificatInfo> VerificatSignList { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,42 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 会话管理服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface ISessionService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 强退会话 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">用户ID</param> | ||||||
|  |     Task ExitSessionAsync(long input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 强退verificat | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">verificat列表</param> | ||||||
|  |     Task ExitVerificatAsync(ExitVerificatInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 会话分页查询 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">查询参数</param> | ||||||
|  |     /// <returns>会话列表</returns> | ||||||
|  |     Task<SqlSugarPagedList<SessionOutput>> PageAsync(SessionPageInput input); | ||||||
|  | } | ||||||
| @@ -0,0 +1,118 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using SqlSugar; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// <inheritdoc cref="ISessionService"/> | ||||||
|  | /// </summary> | ||||||
|  | [Injection(Proxy = typeof(OperDispatchProxy))] | ||||||
|  | public class SessionService : DbRepository<SysUser>, ISessionService | ||||||
|  | { | ||||||
|  |     private readonly INoticeService _noticeService; | ||||||
|  |     private readonly IVerificatService _verificatService; | ||||||
|  |  | ||||||
|  |     /// <inheritdoc cref="ISessionService"/> | ||||||
|  |     public SessionService(IVerificatService verificatService, INoticeService noticeService) | ||||||
|  |     { | ||||||
|  |         _verificatService = verificatService; | ||||||
|  |         _noticeService = noticeService; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("强退会话")] | ||||||
|  |     public async Task ExitSessionAsync(long input) | ||||||
|  |     { | ||||||
|  |         //verificat列表 | ||||||
|  |         List<VerificatInfo> verificatInfos = await _verificatService.GetVerificatIdAsync(input); | ||||||
|  |         //从列表中删除 | ||||||
|  |         await _verificatService.SetVerificatIdAsync(input, new()); | ||||||
|  |         var message = "您已被强制下线!"; | ||||||
|  |         await _noticeService.LogoutAsync(input, verificatInfos, message);//通知下线 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("强退令牌")] | ||||||
|  |     public async Task ExitVerificatAsync(ExitVerificatInput input) | ||||||
|  |     { | ||||||
|  |         //获取该用户的verificat信息 | ||||||
|  |         List<VerificatInfo> verificatInfos = await _verificatService.GetVerificatIdAsync(input.Id); | ||||||
|  |  | ||||||
|  |         //踢掉包含verificat列表的verificat信息 | ||||||
|  |         var setVerificats = verificatInfos.Where(it => !input.VerificatIds.Contains(it.Id)).ToList(); | ||||||
|  |         var deleteVerificats = verificatInfos.Where(it => input.VerificatIds.Contains(it.Id)).ToList(); | ||||||
|  |         await _verificatService.SetVerificatIdAsync(input.Id, setVerificats);//如果还有verificat则更新verificat | ||||||
|  |  | ||||||
|  |         var message = "您已被强制下线!"; | ||||||
|  |         await _noticeService.LogoutAsync(input.Id, deleteVerificats, message);//通知下线 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<SqlSugarPagedList<SessionOutput>> PageAsync(SessionPageInput input) | ||||||
|  |     { | ||||||
|  |         var query = Context.Queryable<SysUser>() | ||||||
|  |               .WhereIF(!string.IsNullOrEmpty(input.Account), it => it.Account.Contains(input.Account))//根据账号查询 | ||||||
|  |               .WhereIF(!string.IsNullOrEmpty(input.LatestLoginIp), it => it.LatestLoginIp.Contains(input.LatestLoginIp))//根据IP查询 | ||||||
|  |               .OrderBy(it => it.LatestLoginTime, OrderByType.Desc) | ||||||
|  |               .Select<SessionOutput>() | ||||||
|  |               .Mapper(async it => | ||||||
|  |               { | ||||||
|  |                   var verificatInfos = await _verificatService.GetVerificatIdAsync(it.Id); | ||||||
|  |                   if (verificatInfos != null) | ||||||
|  |                   { | ||||||
|  |                       GetVerificatInfos(ref verificatInfos);//获取剩余时间 | ||||||
|  |                       it.VerificatCount = verificatInfos.Count;//令牌数量 | ||||||
|  |                       it.VerificatSignList = verificatInfos;//令牌列表 | ||||||
|  |  | ||||||
|  |                       //如果有客户端ID就是在线 | ||||||
|  |                       it.OnlineStatus = verificatInfos.Any(it => it.ClientIds.Count > 0); | ||||||
|  |                   } | ||||||
|  |                   else | ||||||
|  |                   { | ||||||
|  |                       it.VerificatSignList = new(); | ||||||
|  |                   } | ||||||
|  |  | ||||||
|  |               }); | ||||||
|  |         for (int i = 0; i < input.SortField.Count; i++) | ||||||
|  |         { | ||||||
|  |             query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页 | ||||||
|  |         return pageInfo; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #region 方法 | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取verificat剩余时间信息 | ||||||
|  |     /// </summary> | ||||||
|  |     private void GetVerificatInfos(ref List<VerificatInfo> verificatInfos) | ||||||
|  |     { | ||||||
|  |         verificatInfos.ForEach(it => | ||||||
|  |         { | ||||||
|  |             var now = SysDateTimeExtensions.CurrentDateTime; | ||||||
|  |             it.VerificatRemain = now.GetDiffTime(it.VerificatTimeout);//获取时间差 | ||||||
|  |             var verificatSecond = it.VerificatTimeout.AddMinutes(-it.Expire).ToLong();//颁发时间转为时间戳 | ||||||
|  |             var timeoutSecond = it.VerificatTimeout.ToLong();//过期时间转为时间戳 | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #endregion 方法 | ||||||
|  | } | ||||||
| @@ -0,0 +1,69 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 单页输入参数 | ||||||
|  | /// </summary> | ||||||
|  | public class SpaAddInput : SysResource | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 路径 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "Component不能为空")] | ||||||
|  |     public override string Component { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 图标 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "Icon不能为空")] | ||||||
|  |     public override string Icon { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 菜单类型 | ||||||
|  |     /// </summary> | ||||||
|  |     public override TargetTypeEnum TargetType { get; set; } = TargetTypeEnum.SELF; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 标题 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "Title不能为空")] | ||||||
|  |     public override string Title { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 单页输入参数 | ||||||
|  | /// </summary> | ||||||
|  | public class SpaPageInput : BasePageInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 跳转类型 | ||||||
|  |     /// </summary> | ||||||
|  |     public TargetTypeEnum TargetType { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 单页修改参数 | ||||||
|  | /// </summary> | ||||||
|  | public class SpaEditInput : SpaAddInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// ID | ||||||
|  |     /// </summary> | ||||||
|  |     [MinValue(1, ErrorMessage = "Id不能为空")] | ||||||
|  |     public override long Id { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,51 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 单页服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface ISpaService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加单页 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">添加参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task AddAsync(SpaAddInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 删除单页 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">删除参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task DeleteAsync(params long[] input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 编辑单页 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">编辑参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task EditAsync(SpaEditInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 分页查询 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<SqlSugarPagedList<SysResource>> PageAsync(SpaPageInput input); | ||||||
|  | } | ||||||
| @@ -0,0 +1,127 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  | using Furion.FriendlyException; | ||||||
|  |  | ||||||
|  | using Mapster; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | using Yitter.IdGenerator; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// <inheritdoc cref="ISpaService"/> | ||||||
|  | /// </summary> | ||||||
|  | [Injection(Proxy = typeof(OperDispatchProxy))] | ||||||
|  | public class SpaService : DbRepository<SysResource>, ISpaService | ||||||
|  | { | ||||||
|  |     private readonly IResourceService _resourceService; | ||||||
|  |  | ||||||
|  |     /// <inheritdoc cref="ISpaService"/> | ||||||
|  |     public SpaService(IResourceService resourceService) | ||||||
|  |     { | ||||||
|  |         this._resourceService = resourceService; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     [OperDesc("添加单页")] | ||||||
|  |     public async Task AddAsync(SpaAddInput input) | ||||||
|  |     { | ||||||
|  |         CheckInput(input);//检查参数 | ||||||
|  |         input.Code = YitIdHelper.NextId().ToString();//code取随机值 | ||||||
|  |         var sysResource = input.Adapt<SysResource>();//实体转换 | ||||||
|  |         if (await InsertAsync(sysResource))//插入数据 | ||||||
|  |             _resourceService.RefreshCache(ResourceCategoryEnum.SPA);//刷新缓存 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     [OperDesc("删除单页")] | ||||||
|  |     public async Task DeleteAsync(params long[] input) | ||||||
|  |     { | ||||||
|  |         //获取所有ID | ||||||
|  |         var ids = input.ToList(); | ||||||
|  |         if (ids.Count > 0) | ||||||
|  |         { | ||||||
|  |             //获取所有 | ||||||
|  |             var resourceList = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.SPA); | ||||||
|  |             //找到要删除的 | ||||||
|  |             var sysresources = resourceList.Where(it => ids.Contains(it.Id)).ToList(); | ||||||
|  |             //查找内置单页面 | ||||||
|  |             var system = sysresources.Where(it => it.Code == ResourceConst.System).FirstOrDefault(); | ||||||
|  |             if (system != null) | ||||||
|  |                 throw Oops.Bah($"不可删除系统内置单页面:{system.Title}"); | ||||||
|  |             //删除菜单 | ||||||
|  |             await DeleteAsync(sysresources); | ||||||
|  |             _resourceService.RefreshCache(ResourceCategoryEnum.SPA);//刷新缓存 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     [OperDesc("编辑单页")] | ||||||
|  |     public async Task EditAsync(SpaEditInput input) | ||||||
|  |     { | ||||||
|  |         CheckInput(input);//检查参数 | ||||||
|  |         var sysResource = input.Adapt<SysResource>();//实体转换 | ||||||
|  |         if (await UpdateAsync(sysResource))//更新数据 | ||||||
|  |             _resourceService.RefreshCache(ResourceCategoryEnum.SPA);//刷新缓存 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<SqlSugarPagedList<SysResource>> PageAsync(SpaPageInput input) | ||||||
|  |     { | ||||||
|  |         var query = Context.Queryable<SysResource>() | ||||||
|  |                          .Where(it => it.Category == ResourceCategoryEnum.SPA)//单页 | ||||||
|  |                          .WhereIF(input.TargetType != 0, it => it.TargetType == input.TargetType)//根据菜单类型查询 | ||||||
|  |                          .WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Title.Contains(input.SearchKey) || it.Component.Contains(input.SearchKey));//根据关键字查询 | ||||||
|  |         for (int i = 0; i < input.SortField.Count; i++) | ||||||
|  |         { | ||||||
|  |             query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}"); | ||||||
|  |         } | ||||||
|  |         query = query.OrderBy(it => it.SortCode);//排序 | ||||||
|  |  | ||||||
|  |         var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页 | ||||||
|  |         return pageInfo; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #region 方法 | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 检查输入参数 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="sysResource"></param> | ||||||
|  |     private void CheckInput(SysResource sysResource) | ||||||
|  |     { | ||||||
|  |         //判断菜单类型 | ||||||
|  |         if (sysResource.TargetType == TargetTypeEnum.SELF) | ||||||
|  |         { | ||||||
|  |             if (string.IsNullOrEmpty(sysResource.Component)) | ||||||
|  |             { | ||||||
|  |                 throw Oops.Bah($"组件地址不能为空"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else if (sysResource.TargetType == TargetTypeEnum.BLANK)//如果是内链或者外链 | ||||||
|  |         { | ||||||
|  |             sysResource.Component = null; | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             throw Oops.Bah($"单页类型错误:{sysResource.TargetType}");//都不是 | ||||||
|  |         } | ||||||
|  |         //设置为单页 | ||||||
|  |         sysResource.Category = ResourceCategoryEnum.SPA; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #endregion 方法 | ||||||
|  | } | ||||||
| @@ -0,0 +1,78 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using SqlSugar; | ||||||
|  |  | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 添加用户参数 | ||||||
|  | /// </summary> | ||||||
|  | public class UserAddInput : SysUser | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 账号 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "账号不能为空"), MinLength(3, ErrorMessage = "账号不能少于4个字符")] | ||||||
|  |     public override string Account { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 编辑用户参数 | ||||||
|  | /// </summary> | ||||||
|  | public class UserEditInput : UserAddInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 账号 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "账号不能为空"), MinLength(3, ErrorMessage = "账号不能少于4个字符")] | ||||||
|  |     public override string Account { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Id | ||||||
|  |     /// </summary> | ||||||
|  |     [MinValue(1, ErrorMessage = "Id不能为空")] | ||||||
|  |     public override long Id { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 用户分页查询参数 | ||||||
|  | /// </summary> | ||||||
|  | public class UserPageInput : BasePageInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 动态查询条件 | ||||||
|  |     /// </summary> | ||||||
|  |     public Expressionable<SysUser> Expression { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 用户授权角色参数 | ||||||
|  | /// </summary> | ||||||
|  | public class UserGrantRoleInput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Id | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "Id不能为空")] | ||||||
|  |     public long Id { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 授权权限信息 | ||||||
|  |     /// </summary> | ||||||
|  |     [Required(ErrorMessage = "RoleIdList不能为空")] | ||||||
|  |     public List<long> RoleIdList { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,35 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 选择用户输出参数 | ||||||
|  | /// </summary> | ||||||
|  | public class UserSelectorOutput | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 账号 | ||||||
|  |     /// </summary> | ||||||
|  |     public string Account { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Id | ||||||
|  |     /// </summary> | ||||||
|  |     public long Id { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 姓名 | ||||||
|  |     /// </summary> | ||||||
|  |     public string Name { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -0,0 +1,126 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 用户服务 | ||||||
|  | /// </summary> | ||||||
|  | public interface ISysUserService : ITransient | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加用户 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">添加参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task AddAsync(UserAddInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 删除用户 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">Id列表</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task DeleteAsync(params long[] input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 从cache中删除用户信息 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="ids">用户ID列表</param> | ||||||
|  |     void DeleteUserFromCache(params long[] ids); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 禁用用户 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">用户Id</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task DisableUserAsync(long input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 编辑 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">编辑参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task EditAsync(UserEditInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 启用用户 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">用户Id</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task EnableUserAsync(long input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据用户ID获取按钮ID集合 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="userId"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<string>> GetButtonCodeListAsync(long userId); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///根据用户账号获取用户ID | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="account">用户账号</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<long> GetIdByAccountAsync(string account); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据账号获取用户信息 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="account">用户名</param> | ||||||
|  |     /// <returns>用户信息</returns> | ||||||
|  |     Task<SysUser> GetUserByAccountAsync(string account); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据ID获取用户信息 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="Id">用户ID</param> | ||||||
|  |     /// <returns>用户信息</returns> | ||||||
|  |     Task<SysUser> GetUserByIdAsync(long Id); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 给用户授权角色 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">授权参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task GrantRoleAsync(UserGrantRoleInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取用户拥有角色 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">用户ID</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<long>> OwnRoleAsync(BaseIdInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户分页查询 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">查询参数</param> | ||||||
|  |     /// <returns>用户分页列表</returns> | ||||||
|  |     Task<SqlSugarPagedList<SysUser>> PageAsync(UserPageInput input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 重置密码 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="input">用户Id</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task ResetPasswordAsync(long input); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 用户选择器 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     Task<List<UserSelectorOutput>> UserSelectorAsync(string searchKey); | ||||||
|  | } | ||||||
| @@ -0,0 +1,417 @@ | |||||||
|  | #region copyright | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | #endregion | ||||||
|  |  | ||||||
|  | using Furion.DataEncryption; | ||||||
|  | using Furion.DependencyInjection; | ||||||
|  | using Furion.FriendlyException; | ||||||
|  |  | ||||||
|  | using Mapster; | ||||||
|  |  | ||||||
|  | using SqlSugar; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Admin.Core; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | /// <inheritdoc cref="ISysUserService"/> | ||||||
|  | [Injection(Proxy = typeof(OperDispatchProxy))] | ||||||
|  | public class SysUserService : DbRepository<SysUser>, ISysUserService | ||||||
|  | { | ||||||
|  |     private readonly IConfigService _configService; | ||||||
|  |     private readonly IRelationService _relationService; | ||||||
|  |     private readonly IResourceService _resourceService; | ||||||
|  |     private readonly IRoleService _roleService; | ||||||
|  |     private readonly IVerificatService _verificatService; | ||||||
|  |     /// <inheritdoc cref="ISysUserService"/> | ||||||
|  |     public SysUserService( | ||||||
|  |                        IRelationService relationService, | ||||||
|  |                        IResourceService resourceService, | ||||||
|  |                        IVerificatService verificatService, | ||||||
|  |                        IRoleService roleService, | ||||||
|  |                        IConfigService configService) | ||||||
|  |     { | ||||||
|  |         _relationService = relationService; | ||||||
|  |         _resourceService = resourceService; | ||||||
|  |         _roleService = roleService; | ||||||
|  |         _configService = configService; | ||||||
|  |         _verificatService = verificatService; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("添加用户")] | ||||||
|  |     public async Task AddAsync(UserAddInput input) | ||||||
|  |     { | ||||||
|  |         await CheckInputAsync(input);//检查参数 | ||||||
|  |         var account_Id = await GetIdByAccountAsync(input.Account); | ||||||
|  |         if (account_Id > 0) | ||||||
|  |             throw Oops.Bah($"存在重复的账号:{input.Account}"); | ||||||
|  |  | ||||||
|  |         var sysUser = input.Adapt<SysUser>();//实体转换 | ||||||
|  |  | ||||||
|  |         //获取默认密码 | ||||||
|  |         sysUser.Password = await GetDefaultPassWord();//设置密码 | ||||||
|  |         sysUser.UserEnable = true;//默认状态 | ||||||
|  |         await InsertAsync(sysUser);//添加数据 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("删除用户")] | ||||||
|  |     public async Task DeleteAsync(params long[] ids) | ||||||
|  |     { | ||||||
|  |         //获取所有ID | ||||||
|  |         if (ids.Length > 0) | ||||||
|  |         { | ||||||
|  |             var containsSuperAdmin = await IsAnyAsync(it => it.Account == RoleConst.SuperAdmin && ids.Contains(it.Id));//判断是否有超管 | ||||||
|  |             if (containsSuperAdmin) | ||||||
|  |                 throw Oops.Bah($"不可删除系统内置超管用户"); | ||||||
|  |             if (ids.Contains(UserManager.UserId)) | ||||||
|  |                 throw Oops.Bah($"不可删除自己"); | ||||||
|  |  | ||||||
|  |             var result = await DeleteByIdsAsync(ids.Cast<object>().ToArray()); | ||||||
|  |             if (result) | ||||||
|  |             { | ||||||
|  |                 //从列表中删除 | ||||||
|  |                 foreach (var id in ids) | ||||||
|  |                 { | ||||||
|  |                     await _verificatService.SetVerificatIdAsync(id, new()); | ||||||
|  |                 } | ||||||
|  |                 DeleteUserFromCache(ids); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public void DeleteUserFromCache(params long[] ids) | ||||||
|  |     { | ||||||
|  |         List<SysUser> sysUsers = new(); | ||||||
|  |         foreach (var item in ids) | ||||||
|  |         { | ||||||
|  |             var user = CacheStatic.Cache.Get<SysUser>(CacheConst.CACHE_SYSUSER + item, false);//获取用户列表 | ||||||
|  |             sysUsers.Add(user); | ||||||
|  |             //删除账号 | ||||||
|  |             CacheStatic.Cache.Remove(CacheConst.CACHE_SYSUSER + item); | ||||||
|  |         } | ||||||
|  |         sysUsers = sysUsers.Where(it => it != null).ToList();//过滤掉不存在的 | ||||||
|  |         if (sysUsers.Count > 0) | ||||||
|  |         { | ||||||
|  |             var accounts = sysUsers.Select(it => it.Account).ToArray();//账号集合 | ||||||
|  |             foreach (var item in accounts) | ||||||
|  |             { | ||||||
|  |                 //删除账号 | ||||||
|  |                 CacheStatic.Cache.Remove(CacheConst.CAHCE_SYSUSERACCOUNT + item); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("禁用用户")] | ||||||
|  |     public async Task DisableUserAsync(long input) | ||||||
|  |     { | ||||||
|  |         var sysUser = await GetUserByIdAsync(input);//获取用户信息 | ||||||
|  |         if (sysUser != null) | ||||||
|  |         { | ||||||
|  |             var isSuperAdmin = sysUser.Account == RoleConst.SuperAdmin;//判断是否有超管 | ||||||
|  |             if (isSuperAdmin) | ||||||
|  |                 throw Oops.Bah($"不可禁用系统内置超管用户账号"); | ||||||
|  |             CheckSelf(input, AdminConst.Disable);//判断是不是自己 | ||||||
|  |                                                  //设置状态为禁用 | ||||||
|  |             if (await UpdateAsync(it => new SysUser { UserEnable = false }, it => it.Id == input)) | ||||||
|  |             { | ||||||
|  |                 //从列表中删除 | ||||||
|  |                 await _verificatService.SetVerificatIdAsync(input, new()); | ||||||
|  |                 DeleteUserFromCache(input);//从cache删除用户信息 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("编辑用户")] | ||||||
|  |     public async Task EditAsync(UserEditInput input) | ||||||
|  |     { | ||||||
|  |         await CheckInputAsync(input);//检查参数 | ||||||
|  |         var exist = await GetUserByIdAsync(input.Id);//获取用户信息 | ||||||
|  |         if (exist != null) | ||||||
|  |         { | ||||||
|  |             var isSuperAdmin = exist.Account == RoleConst.SuperAdmin;//判断是否有超管 | ||||||
|  |             if (isSuperAdmin && !UserManager.IsSuperAdmin) | ||||||
|  |                 throw Oops.Bah($"不可修改系统内置超管用户账号"); | ||||||
|  |             var sysUser = input.Adapt<SysUser>();//实体转换 | ||||||
|  |             if (await Context.Updateable(sysUser).IgnoreColumns(it => | ||||||
|  |             new | ||||||
|  |             { | ||||||
|  |                 //忽略更新字段 | ||||||
|  |                 it.Password, | ||||||
|  |                 it.LastLoginDevice, | ||||||
|  |                 it.LastLoginIp, | ||||||
|  |                 it.LastLoginTime, | ||||||
|  |                 it.LatestLoginDevice, | ||||||
|  |                 it.LatestLoginIp, | ||||||
|  |                 it.LatestLoginTime | ||||||
|  |             }).ExecuteCommandAsync() > 0)//修改数据 | ||||||
|  |                 DeleteUserFromCache(sysUser.Id);//用户缓存到cache | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("启用用户")] | ||||||
|  |     public async Task EnableUserAsync(long input) | ||||||
|  |     { | ||||||
|  |         CheckSelf(input, AdminConst.Enable);//判断是不是自己 | ||||||
|  |  | ||||||
|  |         //设置状态为启用 | ||||||
|  |         if (await UpdateAsync(it => new SysUser { UserEnable = true }, it => it.Id == input)) | ||||||
|  |             DeleteUserFromCache(input);//从cache删除用户信息 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<string>> GetButtonCodeListAsync(long userId) | ||||||
|  |     { | ||||||
|  |         List<string> buttonCodeList = new();//按钮ID集合 | ||||||
|  |  | ||||||
|  |         //获取关系集合 | ||||||
|  |         var roleList = await _relationService.GetRelationListByObjectIdAndCategoryAsync(userId, CateGoryConst.Relation_SYS_USER_HAS_ROLE); | ||||||
|  |         var roleIdList = roleList.Select(x => x.TargetId.ToLong()).ToList();//角色ID列表 | ||||||
|  |         if (roleIdList.Count > 0)//如果该用户有角色 | ||||||
|  |         { | ||||||
|  |             List<long> buttonIdList = new();//按钮ID集合 | ||||||
|  |             var resourceList = await _relationService.GetRelationListByObjectIdListAndCategoryAsync(roleIdList, CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE);//获取资源集合 | ||||||
|  |             resourceList.ForEach(it => | ||||||
|  |             { | ||||||
|  |                 if (!string.IsNullOrEmpty(it.ExtJson)) buttonIdList.AddRange(it.ExtJson.ToJsonWithT<RelationRoleResuorce>().ButtonInfo);//如果有按钮权限,将按钮ID放到buttonIdList | ||||||
|  |             }); | ||||||
|  |             if (buttonIdList.Count > 0) | ||||||
|  |             { | ||||||
|  |                 buttonCodeList = await _resourceService.GetCodeByIdsAsync(buttonIdList, ResourceCategoryEnum.BUTTON); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return buttonCodeList; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<long> GetIdByAccountAsync(string account) | ||||||
|  |     { | ||||||
|  |         //先从Cache拿 | ||||||
|  |         var userId = CacheStatic.Cache.Get<long>(CacheConst.CAHCE_SYSUSERACCOUNT + account, false); | ||||||
|  |         if (userId == 0) | ||||||
|  |         { | ||||||
|  |             //单查获取用户账号对应ID | ||||||
|  |             userId = await GetFirstAsync(it => it.Account == account, it => it.Id); | ||||||
|  |             if (userId != 0) | ||||||
|  |             { | ||||||
|  |                 //插入Cache | ||||||
|  |                 CacheStatic.Cache.Set(CacheConst.CAHCE_SYSUSERACCOUNT + account, userId, false); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return userId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<string>> GetPermissionListByUserIdAsync(long userId) | ||||||
|  |     { | ||||||
|  |         var permissions = new List<string>();//权限集合 | ||||||
|  |         var roleIdList = await _relationService.GetRelationListByObjectIdAndCategoryAsync(userId, CateGoryConst.Relation_SYS_USER_HAS_ROLE);//根据用户ID获取角色ID | ||||||
|  |         if (roleIdList.Count > 0)//如果角色ID不为空 | ||||||
|  |         { | ||||||
|  |             //获取角色权限信息 | ||||||
|  |             var sysRelations = await _relationService.GetRelationListByObjectIdListAndCategoryAsync(roleIdList.Select(it => it.TargetId.ToLong()).ToList(), CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION); | ||||||
|  |             var relationGroup = sysRelations.GroupBy(it => it.TargetId).ToList();//根据目标ID,也就是接口名分组,因为存在一个用户多个角色 | ||||||
|  |  | ||||||
|  |             //遍历分组 | ||||||
|  |             relationGroup.ForEach(it => | ||||||
|  |             { | ||||||
|  |                 HashSet<string> scopeSet = new();//定义不可重复列表 | ||||||
|  |                 var relationList = it.ToList();//关系列表 | ||||||
|  |                 relationList.ForEach(it => | ||||||
|  |                 { | ||||||
|  |                     var rolePermission = it.ExtJson.ToJsonWithT<RelationRolePermission>(); | ||||||
|  |                     scopeSet.Add(rolePermission.ApiUrl); | ||||||
|  |                 }); | ||||||
|  |                 permissions.AddRange(scopeSet);//将改URL的权限集合加入权限集合列表 | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |         return permissions; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<SysUser> GetUserByAccountAsync(string account) | ||||||
|  |     { | ||||||
|  |         var userId = await GetIdByAccountAsync(account);//获取用户ID | ||||||
|  |         if (userId > 0) | ||||||
|  |         { | ||||||
|  |             var sysUser = await GetUserByIdAsync(userId);//获取用户信息 | ||||||
|  |             if (sysUser.Account == account)//这里做了比较用来限制大小写 | ||||||
|  |                 return sysUser; | ||||||
|  |             else | ||||||
|  |                 return null; | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<SysUser> GetUserByIdAsync(long Id) | ||||||
|  |     { | ||||||
|  |         //先从Cache拿,需要获取新的对象,避免操作导致缓存中对象改变 | ||||||
|  |         var sysUser = CacheStatic.Cache.Get<SysUser>(CacheConst.CACHE_SYSUSER + Id.ToString(), true); | ||||||
|  |         if (sysUser == null) | ||||||
|  |         { | ||||||
|  |             sysUser = await Context.Queryable<SysUser>() | ||||||
|  |             .Where(u => u.Id == Id) | ||||||
|  |             .FirstAsync(); | ||||||
|  |             if (sysUser != null) | ||||||
|  |             { | ||||||
|  |                 //获取按钮码 | ||||||
|  |                 var buttonCodeList = await GetButtonCodeListAsync(sysUser.Id); | ||||||
|  |                 //获取角色码 | ||||||
|  |                 var roleCodeList = await _roleService.GetRoleListByUserIdAsync(sysUser.Id); | ||||||
|  |                 //获取权限码 | ||||||
|  |                 var permissionCodeList = await GetPermissionListByUserIdAsync(sysUser.Id); | ||||||
|  |  | ||||||
|  |                 //权限码赋值 | ||||||
|  |                 sysUser.ButtonCodeList = buttonCodeList; | ||||||
|  |                 sysUser.RoleCodeList = roleCodeList.Select(it => it.Code).ToList(); | ||||||
|  |                 sysUser.RoleIdList = roleCodeList.Select(it => it.Id).ToList(); | ||||||
|  |                 sysUser.PermissionCodeList = permissionCodeList; | ||||||
|  |                 //插入Cache | ||||||
|  |                 CacheStatic.Cache.Set(CacheConst.CACHE_SYSUSER + sysUser.Id.ToString(), sysUser, true); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return sysUser; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     [OperDesc("用户授权")] | ||||||
|  |     public async Task GrantRoleAsync(UserGrantRoleInput input) | ||||||
|  |     { | ||||||
|  |         var sysUser = await GetUserByIdAsync(input.Id);//获取用户信息 | ||||||
|  |         if (sysUser != null) | ||||||
|  |         { | ||||||
|  |             var isSuperAdmin = sysUser.Account == RoleConst.SuperAdmin;//判断是否有超管 | ||||||
|  |             if (isSuperAdmin) | ||||||
|  |                 throw Oops.Bah($"不能给超管分配角色"); | ||||||
|  |             CheckSelf(input.Id, AdminConst.GrantRole);//判断是不是自己 | ||||||
|  |  | ||||||
|  |             //给用户赋角色 | ||||||
|  |             await _relationService.SaveRelationBatchAsync(CateGoryConst.Relation_SYS_USER_HAS_ROLE, input.Id, input.RoleIdList.Select(it => it.ToString()).ToList(), null, true); | ||||||
|  |             DeleteUserFromCache(input.Id);//从cache删除用户信息 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<long>> OwnRoleAsync(BaseIdInput input) | ||||||
|  |     { | ||||||
|  |         var relations = await _relationService.GetRelationListByObjectIdAndCategoryAsync(input.Id, CateGoryConst.Relation_SYS_USER_HAS_ROLE); | ||||||
|  |         return relations.Select(it => it.TargetId.ToLong()).ToList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<SqlSugarPagedList<SysUser>> PageAsync(UserPageInput input) | ||||||
|  |     { | ||||||
|  |         var query = Context.Queryable<SysUser>() | ||||||
|  |          .WhereIF(input.Expression != null, input.Expression?.ToExpression())//动态查询 | ||||||
|  |          .WhereIF(!string.IsNullOrEmpty(input.SearchKey), u => u.Account.Contains(input.SearchKey))//根据关键字查询 | ||||||
|  |          .Mapper(u => | ||||||
|  |          { | ||||||
|  |              u.Password = null;//密码清空 | ||||||
|  |          }); | ||||||
|  |         for (int i = 0; i < input.SortField.Count; i++) | ||||||
|  |         { | ||||||
|  |             query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}"); | ||||||
|  |         } | ||||||
|  |         query = query.OrderBy(it => it.SortCode);//排序 | ||||||
|  |         query = query.OrderBy(u => u.Id);//排序 | ||||||
|  |  | ||||||
|  |         var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页 | ||||||
|  |         return pageInfo; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     [OperDesc("重置密码")] | ||||||
|  |     public async Task ResetPasswordAsync(long input) | ||||||
|  |     { | ||||||
|  |         var password = await GetDefaultPassWord(true);//获取默认密码,这里不走Aop所以需要加密一下 | ||||||
|  |         //重置密码 | ||||||
|  |         if (await UpdateAsync(it => new SysUser { Password = password }, it => it.Id == input)) | ||||||
|  |         { | ||||||
|  |             //从列表中删除 | ||||||
|  |             await _verificatService.SetVerificatIdAsync(input, new()); | ||||||
|  |             DeleteUserFromCache(input);//从cache删除用户信息 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<List<UserSelectorOutput>> UserSelectorAsync(string searchKey) | ||||||
|  |     { | ||||||
|  |         var result = await Context.Queryable<SysUser>() | ||||||
|  |                          .WhereIF(!string.IsNullOrEmpty(searchKey), it => it.Account.Contains(searchKey))//根据关键字查询 | ||||||
|  |                          .Select<UserSelectorOutput>()//映射成SysUserSelectorOutput | ||||||
|  |                          .ToListAsync(); | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #region 方法 | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 检查输入参数 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="sysUser"></param> | ||||||
|  |     private async Task CheckInputAsync(SysUser sysUser) | ||||||
|  |     { | ||||||
|  |         //判断账号重复,直接从cache拿 | ||||||
|  |         var account_Id = await GetIdByAccountAsync(sysUser.Account); | ||||||
|  |         if (account_Id > 0 && account_Id != sysUser.Id) | ||||||
|  |             throw Oops.Bah($"存在重复的账号:{sysUser.Account}"); | ||||||
|  |         //如果手机号不是空 | ||||||
|  |         if (!string.IsNullOrEmpty(sysUser.Phone)) | ||||||
|  |         { | ||||||
|  |             if (!sysUser.Phone.MatchPhoneNumber())//验证手机格式 | ||||||
|  |                 throw Oops.Bah($"手机号码:{sysUser.Phone} 格式错误"); | ||||||
|  |             sysUser.Phone = DESCEncryption.Encrypt(sysUser.Phone, DESCKeyConst.DESCKey); | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |         //如果邮箱不是空 | ||||||
|  |         if (!string.IsNullOrEmpty(sysUser.Email)) | ||||||
|  |         { | ||||||
|  |             var ismatch = sysUser.Email.MatchEmail();//验证邮箱格式 | ||||||
|  |             if (!ismatch) | ||||||
|  |                 throw Oops.Bah($"邮箱:{sysUser.Email} 格式错误"); | ||||||
|  |             if (await IsAnyAsync(it => it.Email == sysUser.Email && it.Id != sysUser.Id)) | ||||||
|  |                 throw Oops.Bah($"存在重复的邮箱:{sysUser.Email}"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 检查是否为自己 | ||||||
|  |     /// </summary> | ||||||
|  |     private void CheckSelf(long id, string operate) | ||||||
|  |     { | ||||||
|  |         if (id == UserManager.UserId)//如果是自己 | ||||||
|  |         { | ||||||
|  |             throw Oops.Bah($"禁止{operate}自己"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取默认密码 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     private async Task<string> GetDefaultPassWord(bool isSm4 = false) | ||||||
|  |     { | ||||||
|  |         //获取默认密码 | ||||||
|  |         var defaultPassword = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_PASSWORD)).ConfigValue; | ||||||
|  |         return isSm4 ? DESCEncryption.Encrypt(defaultPassword, DESCKeyConst.DESCKey) : defaultPassword;//判断是否需要加密 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #endregion 方法 | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user