Compare commits
	
		
			180 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | fef6259d09 | ||
|   | e24f19e88b | ||
|   | cc6de1e71c | ||
|   | b0240a1c95 | ||
|   | 8acdec333b | ||
|   | eafcbb81f9 | ||
|   | 4b083463c8 | ||
|   | 78e834e04f | ||
|   | 2ed1893620 | ||
|   | 35f188a7dd | ||
|   | 07f577a9dd | ||
|   | 25c72b50c0 | ||
|   | e07346ebea | ||
|   | 699b466291 | ||
|   | f0f24ff96f | ||
|   | 1113997c71 | ||
|   | 487fb6e5c9 | ||
|   | 62908a323c | ||
|   | 021131271a | ||
|   | c20fa4dbd2 | ||
|   | b1826678da | ||
|   | 1630338f4e | ||
|   | 78af3d979c | ||
|   | 8346dfb1f5 | ||
|   | 5ecb4e4fe4 | ||
|   | ec9ff23b23 | ||
|   | 149e9931e7 | ||
|   | 58b62094bd | ||
|   | d357074ad4 | ||
|   | d9bacc129b | ||
|   | 28d869b099 | ||
|   | d04e87718a | ||
|   | 8d81bf60c9 | ||
|   | 4fe9094ff7 | ||
|   | 4cfde45755 | ||
|   | c28844dbeb | ||
|   | 837ea7c8d6 | ||
|   | 6c85ac8827 | ||
|   | c8eb8bf35d | ||
|   | 0f6cbddd57 | ||
|   | 530b4755bf | ||
|   | 679395b3fe | ||
|   | 9293442836 | ||
|   | 4841b412f3 | ||
|   | f03575ad89 | ||
|   | e0e5428ebd | ||
|   | 015f7edae2 | ||
|   | d5414f5d91 | ||
|   | 4bc3512d42 | ||
|   | 4c305994ca | ||
|   | 788e2cba04 | ||
|   | b79b79970e | ||
|   | 8b0f4de516 | ||
|   | a17000ad6d | ||
|   | c9f63c58df | ||
|   | c311b1c706 | ||
|   | 072451afae | ||
|   | 7349793463 | ||
|   | c0144eab86 | ||
|   | ba79b25944 | ||
|   | 3fb34b12a0 | ||
|   | b6c17e76c2 | ||
|   | 1201f20d79 | ||
|   | 98ae8e692e | ||
|   | 8342dbff4e | ||
|   | a22aa0f9a7 | ||
|   | b49d221ec1 | ||
|   | f1be315147 | ||
|   | 8aaac2a3d4 | ||
|   | bd07d30e47 | ||
|   | 54e103f00f | ||
|   | 5404d8f7ab | ||
|   | 1364e02c67 | ||
|   | b4818e2f9a | ||
|   | 734cf57d4a | ||
|   | 9236610ec1 | ||
|   | c33828a5d9 | ||
|   | faadb03e46 | ||
|   | 3a0c4c51d2 | ||
|   | 6c28c2b91e | ||
|   | 5226df6194 | ||
|   | 1ad23a3cbb | ||
|   | c4149ca304 | ||
|   | 68b02fe950 | ||
|   | d9408523a5 | ||
|   | 40ad0f063a | ||
|   | 40d85e8926 | ||
|   | e655f719e6 | ||
|   | ede0ea02c5 | ||
|   | e579575b21 | ||
|   | 06ffcc9fdf | ||
|   | 774d03e510 | ||
|   | adcac9dfe6 | ||
|   | 399e14c70c | ||
|   | ec8b51033b | ||
|   | ca5d167b6a | ||
|   | fcd1b001d1 | ||
|   | 1cabbefe04 | ||
|   | ecefe22c95 | ||
|   | 64db77b9a2 | ||
|   | 19d7360ad5 | ||
|   | 2f014cd827 | ||
|   | 7fb84205d9 | ||
|   | 47e1127c5f | ||
|   | e036b59306 | ||
|   | f4904c3b53 | ||
|   | 3002a63ba5 | ||
|   | b69717e6c3 | ||
|   | d07962953f | ||
|   | 857cf0d21e | ||
|   | 5f08c2615d | ||
|   | e87428ef33 | ||
|   | 03fd54fe70 | ||
|   | 086c2c8253 | ||
|   | 05c19a32ea | ||
|   | 99d174906a | ||
|   | 82e30a326a | ||
|   | 2963a9cdca | ||
|   | 288da75b2b | ||
|   | 0943a496dd | ||
|   | e5dd7cc2fa | ||
|   | 358836ef9f | ||
|   | aab4fac6c5 | ||
|   | 525540b603 | ||
|   | b30eeb4694 | ||
|   | 3faf0aa2fc | ||
|   | fd728dec5d | ||
|   | 08b14b72d4 | ||
|   | de2e005abf | ||
|   | 0fc75239a6 | ||
|   | 390fe30a0d | ||
|   | 633f49fcd2 | ||
|   | 30c0ba93b9 | ||
|   | e935fb9621 | ||
|   | 5ce8bb1d08 | ||
|   | 1acd12980a | ||
|   | 683235dd8a | ||
|   | 65fe183ad4 | ||
|   | f39d5d355c | ||
|   | eccc8e0ff0 | ||
|   | dd4d8e775c | ||
|   | 7a7f857b2f | ||
|   | 10882b7d93 | ||
|   | e669b81005 | ||
|   | 28a81d9539 | ||
|   | 6bf5e4a6b8 | ||
|   | a51eee93f4 | ||
|   | bece2555c2 | ||
|   | d98d405009 | ||
|   | 8d0881632c | ||
|   | e8b81da897 | ||
|   | acf4fbf750 | ||
|   | b4f1921d33 | ||
|   | 94b4816f53 | ||
|   | b6ddafde3e | ||
|   | b01036818f | ||
|   | ad7da1a0c3 | ||
|   | 1e0818d9d9 | ||
|   | a4686f01c3 | ||
|   | 84d76f9aab | ||
|   | b90bf5eb86 | ||
|   | fe258f3fe5 | ||
|   | d339494594 | ||
|   | adfaf13055 | ||
|   | 8abfeb5923 | ||
|   | c1a027a771 | ||
|   | 5f3094d79b | ||
|   | 925b81aca8 | ||
|   | c0c8437966 | ||
|   | 1d27b2fc4a | ||
|   | 5da43e7808 | ||
|   | 5edbb558ae | ||
|   | 7347cc1df2 | ||
|   | 20cac11b2a | ||
|   | 75c35c4ff8 | ||
|   | 9d573512d0 | ||
|   | 7921365853 | ||
|   | 43263fd3b9 | ||
|   | 59042a5ead | ||
|   | 91b14de807 | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -366,6 +366,6 @@ FodyWeavers.xsd | ||||
| /src/*Pro* | ||||
| /src/*pro* | ||||
| /src/*pro*/ | ||||
| /src/ThingsGateway.Web.Entry/.config/ | ||||
| /doc/.* | ||||
|  | ||||
| /src/ThingsGateway.Server/.config/ | ||||
| /src/nuget.exe | ||||
|   | ||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						| @@ -87,7 +87,7 @@ | ||||
|       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 | ||||
|    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: | ||||
|   | ||||
							
								
								
									
										120
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,67 +1,65 @@ | ||||
|  | ||||
| # ThingsGateway | ||||
|  | ||||
| ## Introduction | ||||
|  | ||||
| A cross-platform, high-performance edge data collection gateway based on net8, capable of handling millions of data points per. | ||||
|  | ||||
| ## Documentation | ||||
|  | ||||
| [ThingsGateway Documentation](https://diego2098.gitee.io/thingsgateway-docs/). | ||||
|  | ||||
| [**NuGet Address**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22) | ||||
|  | ||||
| ### Plugin List | ||||
|  | ||||
| #### Data Collection Plugins | ||||
|  | ||||
| ## 介绍 | ||||
|  | ||||
|  **NetCore** 跨平台边缘采集网关(工业设备采集) | ||||
|  | ||||
|  **ThingsGateway** 存储库同时提供 [**设备采集驱动**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22) | ||||
|  | ||||
|  **ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 **ThingsGateway - Admin** | ||||
|  | ||||
|  | ||||
| ## 文档 | ||||
|  | ||||
| [ThingsGateway](https://diego2098.gitee.io/thingsgateway-docs/) 文档。 | ||||
|  | ||||
| ### 插件列表 | ||||
|  | ||||
| #### 采集插件 | ||||
| | 插件名称 | 备注 |  | ||||
| | Plugin Name | Remarks |  | ||||
| |-------|-------| | ||||
| | ModbusMaster | Rtu/Tcp报文格式,支持串口/Tcp/Udp链路 |  | ||||
| | S7 | 西门子PLC S7系列 |  | ||||
| | Dlt6452007 | Master,支持串口/Tcp/Udp链路 |  | ||||
| | OpcDaClient | 64位编译 | | ||||
| | OpcUaClient | 支持证书登录,扩展对象Json读写 | | ||||
| | 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 | | ||||
|  | ||||
| #### 业务插件 | ||||
| | 插件名称 | 备注 |  | ||||
| #### Business Plugins | ||||
|  | ||||
| | Plugin Name | Remarks |  | ||||
| |-------|-------| | ||||
| | ModbusSlave | Rtu/Tcp报文格式,支持串口/Tcp/Udp链路,支持Rpc反写 |  | ||||
| | OpcUaServer | OpcUa服务端,支持Rpc反写 |  | ||||
| | Mqtt Client | Mqtt客户端,支持Rpc反写,脚本自定义上传内容 |  | ||||
| | Mqtt Server | Mqtt服务端,支持WebSocket,支持Rpc反写,脚本自定义上传内容 |  | ||||
| | Kafka Client | 数据生产,脚本自定义上传内容 |  | ||||
| | RabbitMQ Client | 数据生产,脚本自定义上传内容 |  | ||||
| | SqlDB | 关系数据库存储,支持历史存储和实时数据更新 |  | ||||
| | SqlHisAlarm | 报警历史数据关系数据库存储 |  | ||||
| | TDengineDB | 时序数据库存储 |  | ||||
| | QuestDB | 时序数据库存储 |  | ||||
|  | ||||
| ## 协议 | ||||
|  | ||||
| [ThingsGateway](https://gitee.com/diego2098/ThingsGateway) 采用 [Apache-2.0](https://gitee.com/diego2098/ThingsGateway/blob/master/LICENSE) 开源协议。 | ||||
|  | ||||
| ## 演示 | ||||
|  | ||||
| [ThingsGateway演示地址](http://120.24.62.140:5000/) | ||||
|  | ||||
| 账户	:  **superAdmin**	 | ||||
|  | ||||
| 密码 : **111111** | ||||
|  | ||||
| ## 赞助 | ||||
|  | ||||
| [ThingsGateway赞助途径](https://diego2098.gitee.io/thingsgateway-docs/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://diego2098.gitee.io/thingsgateway-docs/docs/1001) | ||||
|  | ||||
|  | ||||
| | 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 |  | ||||
| | SqlHisAlarm | Alarm historical data relational database storage |  | ||||
| | TDengineDB | Time-series database storage |  | ||||
| | QuestDB | Time-series database storage |  | ||||
|  | ||||
|  | ||||
| ## License | ||||
|  | ||||
| [Apache-2.0](https://gitee.com/diego2098/ThingsGateway/blob/master/LICENSE) | ||||
|  | ||||
| ## Demo | ||||
|  | ||||
| [ThingsGateway Demo Address](http://47.119.161.158:5000/) | ||||
|  | ||||
| Account: **SuperAdmin** | ||||
|  | ||||
| Password: **111111** | ||||
|  | ||||
| **In the upper-right corner, switch to the IoT Gateway module in the personal popup box** | ||||
|  | ||||
| ## Sponsorship | ||||
|  | ||||
| [Sponsorship Approach](https://diego2098.gitee.io/thingsgateway-docs/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://diego2098.gitee.io/thingsgateway-docs/docs/1001) | ||||
							
								
								
									
										66
									
								
								README.zh-CN.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,66 @@ | ||||
|  | ||||
| # ThingsGateway | ||||
|  | ||||
| ## 介绍 | ||||
|  | ||||
| 基于net8的跨平台高性能边缘采集网关,单机采集数据点位可达百万 | ||||
|  | ||||
| ## 文档 | ||||
|  | ||||
| [ThingsGateway](https://diego2098.gitee.io/thingsgateway-docs/) 文档。 | ||||
|  | ||||
| [**底层驱动 NuGet地址**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22) | ||||
|  | ||||
| ### 插件列表 | ||||
|  | ||||
| #### 采集插件 | ||||
| | 插件名称 | 备注 |  | ||||
| |-------|-------| | ||||
| | 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 | 关系数据库存储,支持历史存储和实时数据更新 |  | ||||
| | SqlHisAlarm | 报警历史数据关系数据库存储 |  | ||||
| | TDengineDB | 时序数据库存储 |  | ||||
| | QuestDB | 时序数据库存储 |  | ||||
|  | ||||
| ## 协议 | ||||
|  | ||||
| [Apache-2.0](https://gitee.com/diego2098/ThingsGateway/blob/master/LICENSE) | ||||
|  | ||||
| ## 演示 | ||||
|  | ||||
| [ThingsGateway演示地址](http://47.119.161.158:5000/) | ||||
|  | ||||
| 账户	:  **SuperAdmin**	 | ||||
|  | ||||
| 密码 : **111111** | ||||
|  | ||||
| **右上角个人弹出框中,切换到物联网关模块** | ||||
|  | ||||
| ## 赞助 | ||||
|  | ||||
| [赞助途径](https://diego2098.gitee.io/thingsgateway-docs/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://diego2098.gitee.io/thingsgateway-docs/docs/1001) | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -19,7 +19,7 @@ import Highlight from '@site/src/components/Highlight.js'; | ||||
|  | ||||
| ## 一、说明 | ||||
|  | ||||
| **ThingsGateway** 基于NET6、7、8,默认开发IDE为VS2022(**17.8版本以上**),安装VS时需勾选ASP.NET类别. | ||||
| **ThingsGateway** 基于NET6、8,默认开发IDE为VS2022(**17.8版本以上**),安装VS时需勾选ASP.NET类别. | ||||
|  | ||||
| <img src={require("@site/static/img/docs/vs2022install.png").default} /> | ||||
|  | ||||
| @@ -42,7 +42,7 @@ import Highlight from '@site/src/components/Highlight.js'; | ||||
|  | ||||
| <img src={require("@site/static/img/docs/生成解决方案.png").default} /> | ||||
|  | ||||
| 2、 设置ThingsGateway.Web.Entry为启动项目,开始调试或开始执行 | ||||
| 2、 设置ThingsGateway.Server为启动项目,开始调试或开始执行 | ||||
| <img src={require("@site/static/img/docs/设置启动项目.png").default} /> | ||||
| <img src={require("@site/static/img/docs/开始执行.png").default} /> | ||||
|  | ||||
| @@ -54,7 +54,7 @@ import Highlight from '@site/src/components/Highlight.js'; | ||||
|  | ||||
| :::tip 提示 | ||||
|  | ||||
| 测试环境下,账密会自动填充为超级管理员账号,默认账户:**superAdmin**,密码:**111111** | ||||
| 测试环境下,账密会自动填充为超级管理员账号,默认账户:**SuperAdmin**,密码:**111111** | ||||
|  | ||||
| :::   | ||||
|  | ||||
|   | ||||
| @@ -83,7 +83,7 @@ function Banner() { | ||||
|           <div className="ThingsGateway-get-start-btn"> | ||||
|             <Link className="ThingsGateway-get-start" to={useBaseUrl("docs/")}> | ||||
|               入门指南 | ||||
|               <span className="ThingsGateway-version">v5.0</span> | ||||
|               <span className="ThingsGateway-version">v6.0</span> | ||||
|             </Link> | ||||
|           </div> | ||||
|         </div> | ||||
| @@ -114,7 +114,7 @@ function Gitee() { | ||||
|             className={"ThingsGateway-log-jiao" + (isDarkTheme ? " dark" : "")} | ||||
|           ></div> | ||||
|           <div className="ThingsGateway-log-number"> | ||||
|             <div style={{ color: "#723cff" }}>600 +</div> | ||||
|             <div style={{ color: "#723cff" }}>700 +</div> | ||||
|             <span className={isDarkTheme ? " dark" : ""}>Stars</span> | ||||
|           </div> | ||||
|         </div> | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								doc/static/img/docs/login.png
									
									
									
									
										vendored
									
									
								
							
							
						
						| Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 152 KiB | 
							
								
								
									
										
											BIN
										
									
								
								doc/static/img/docs/开始执行.png
									
									
									
									
										vendored
									
									
								
							
							
						
						| Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 19 KiB | 
							
								
								
									
										
											BIN
										
									
								
								doc/static/img/docs/生成解决方案.png
									
									
									
									
										vendored
									
									
								
							
							
						
						| Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 70 KiB | 
							
								
								
									
										
											BIN
										
									
								
								doc/static/img/docs/设置启动项目.png
									
									
									
									
										vendored
									
									
								
							
							
						
						| Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 22 KiB | 
| @@ -1,25 +1,247 @@ | ||||
| [*.cs] | ||||
| root = true | ||||
|  | ||||
| # IDE0290: 使用主构造函数 | ||||
| dotnet_diagnostic.IDE0290.severity = none | ||||
| # To learn more about .editorconfig see https://aka.ms/editorconfigdocs | ||||
| ###############################  | ||||
| # Core EditorConfig Options   #  | ||||
| ###############################  | ||||
|  | ||||
| # IDE0028: 简化集合初始化 | ||||
| dotnet_diagnostic.IDE0028.severity = none | ||||
| # All files  | ||||
| [*] | ||||
| indent_style = space | ||||
| charset = utf-8 | ||||
| trim_trailing_whitespace = true | ||||
| insert_final_newline = true | ||||
| spelling_exclusion_path = .\exclusion.dic | ||||
|  | ||||
| # CA1822: 将成员标记为 static | ||||
| dotnet_diagnostic.CA1822.severity = none | ||||
| # Microsoft .NET properties | ||||
| csharp_new_line_before_members_in_object_initializers = false | ||||
| csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion | ||||
| csharp_style_prefer_utf8_string_literals = true:suggestion | ||||
| csharp_style_var_elsewhere = true:suggestion | ||||
| csharp_style_var_for_built_in_types = true:suggestion | ||||
| csharp_style_var_when_type_is_apparent = true:suggestion | ||||
| dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True | ||||
| dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field | ||||
| dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef | ||||
| dotnet_naming_rule.unity_serialized_field_rule.severity = warning | ||||
| dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style | ||||
| dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols | ||||
| dotnet_naming_style.lower_camel_case_style.capitalization = camel_case | ||||
| dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * | ||||
| dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds =  | ||||
| dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field | ||||
| dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance | ||||
| dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none | ||||
| dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none | ||||
| dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none | ||||
| dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion | ||||
| dotnet_style_predefined_type_for_member_access = true:suggestion | ||||
| dotnet_style_qualification_for_event = false:suggestion | ||||
| dotnet_style_qualification_for_field = false:suggestion | ||||
| dotnet_style_qualification_for_method = false:suggestion | ||||
| dotnet_style_qualification_for_property = false:suggestion | ||||
| dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion | ||||
|  | ||||
| # IDE0052: 删除未读的私有成员 | ||||
| dotnet_diagnostic.IDE0052.severity = none | ||||
| # ReSharper properties | ||||
| resharper_autodetect_indent_settings = true | ||||
| resharper_formatter_off_tag = @formatter:off | ||||
| resharper_formatter_on_tag = @formatter:on | ||||
| resharper_formatter_tags_enabled = true | ||||
| resharper_new_line_before_while = true | ||||
| resharper_place_attribute_on_same_line = false | ||||
| resharper_show_autodetect_configure_formatting_tip = false | ||||
| resharper_use_indent_from_vs = false | ||||
|  | ||||
| # CA2254: 模板应为静态表达式 | ||||
| dotnet_diagnostic.CA2254.severity = none | ||||
| # ReSharper inspection severities | ||||
| resharper_arrange_redundant_parentheses_highlighting = hint | ||||
| resharper_arrange_this_qualifier_highlighting = hint | ||||
| resharper_arrange_type_member_modifiers_highlighting = hint | ||||
| resharper_arrange_type_modifiers_highlighting = hint | ||||
| resharper_built_in_type_reference_style_for_member_access_highlighting = hint | ||||
| resharper_built_in_type_reference_style_highlighting = hint | ||||
| resharper_redundant_base_qualifier_highlighting = warning | ||||
| resharper_suggest_var_or_type_built_in_types_highlighting = hint | ||||
| resharper_suggest_var_or_type_elsewhere_highlighting = hint | ||||
| resharper_suggest_var_or_type_simple_types_highlighting = hint | ||||
| resharper_web_config_module_not_resolved_highlighting = warning | ||||
| resharper_web_config_type_not_resolved_highlighting = warning | ||||
| resharper_web_config_wrong_module_highlighting = warning | ||||
|  | ||||
| # CA1854: 首选 “IDictionary.TryGetValue(TKey, out TValue)” 方法 | ||||
| dotnet_diagnostic.CA1854.severity = none | ||||
| # Code files  | ||||
| [*.{cs,csx,vb,vbx}] | ||||
| indent_size = 4 | ||||
|  | ||||
| # IDE0090: 使用 "new(...)" | ||||
| dotnet_diagnostic.IDE0090.severity = none | ||||
| [*.{cs,css,js,json,*html,razor,txt,log}] | ||||
| charset = utf-8-bom | ||||
|  | ||||
| # IDE0305: 简化集合初始化 | ||||
| dotnet_diagnostic.IDE0305.severity = none | ||||
| [*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}] | ||||
| indent_size = 2 | ||||
|  | ||||
| # Xml config files | ||||
| [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] | ||||
| indent_size = 2 | ||||
|  | ||||
| [*.json] | ||||
| indent_size = 2 | ||||
|  | ||||
| [*.{ps1,psm1}] | ||||
| indent_size = 4 | ||||
|  | ||||
| [*.sh] | ||||
| indent_size = 4 | ||||
| end_of_line = lf | ||||
|  | ||||
| ###############################  | ||||
| # .NET Coding Conventions     #  | ||||
| ###############################  | ||||
| [*.{cs,vb}] | ||||
| # Organize usings  | ||||
| dotnet_sort_system_directives_first = false | ||||
| # this. preferences  | ||||
| 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 | ||||
| # Language keywords vs BCL types preferences  | ||||
| dotnet_style_predefined_type_for_locals_parameters_members = true:silent | ||||
| dotnet_style_predefined_type_for_member_access = true:silent | ||||
| # Parentheses preferences  | ||||
| dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent | ||||
| dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent | ||||
| dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent | ||||
| dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent | ||||
| # Modifier preferences  | ||||
| dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent | ||||
| dotnet_style_readonly_field = true:suggestion | ||||
| # Expression-level preferences  | ||||
| dotnet_style_object_initializer = true:suggestion | ||||
| dotnet_style_collection_initializer = true:suggestion | ||||
| dotnet_style_explicit_tuple_names = true:suggestion | ||||
| dotnet_style_null_propagation = true:suggestion | ||||
| dotnet_style_coalesce_expression = true:suggestion | ||||
| dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent | ||||
| dotnet_style_prefer_inferred_tuple_names = true:suggestion | ||||
| dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion | ||||
| dotnet_style_prefer_auto_properties = true:silent | ||||
| dotnet_style_prefer_conditional_expression_over_assignment = true:silent | ||||
| dotnet_style_prefer_conditional_expression_over_return = true:silent | ||||
| ###############################  | ||||
| # Naming Conventions          #  | ||||
| ###############################  | ||||
| # Style Definitions  | ||||
| dotnet_naming_style.pascal_case_style.capitalization = pascal_case | ||||
| # Use PascalCase for constant fields    | ||||
| dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion | ||||
| dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields | ||||
| dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style | ||||
| dotnet_naming_symbols.constant_fields.applicable_kinds = field | ||||
| dotnet_naming_symbols.constant_fields.applicable_accessibilities = * | ||||
| dotnet_naming_symbols.constant_fields.required_modifiers = const | ||||
| dotnet_style_operator_placement_when_wrapping = beginning_of_line | ||||
| tab_width = 4 | ||||
| end_of_line = crlf | ||||
| dotnet_style_prefer_collection_expression = true:suggestion | ||||
| dotnet_style_prefer_simplified_boolean_expressions = true:suggestion | ||||
| dotnet_style_prefer_compound_assignment = true:suggestion | ||||
| dotnet_style_prefer_simplified_interpolation = true:suggestion | ||||
| dotnet_style_namespace_match_folder = true:suggestion | ||||
| dotnet_style_allow_multiple_blank_lines_experimental = true:silent | ||||
| dotnet_style_allow_statement_immediately_after_block_experimental = true:silent | ||||
| dotnet_code_quality_unused_parameters = all:suggestion | ||||
| ###############################  | ||||
| # C# Coding Conventions       #  | ||||
| ###############################  | ||||
| [*.cs] | ||||
| # var preferences  | ||||
| csharp_style_var_for_built_in_types = true:silent | ||||
| csharp_style_var_when_type_is_apparent = true:silent | ||||
| csharp_style_var_elsewhere = true:silent | ||||
| csharp_prefer_static_local_function = true:silent | ||||
| # Expression-bodied members  | ||||
| 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 | ||||
| # Pattern matching preferences  | ||||
| csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion | ||||
| csharp_style_pattern_matching_over_as_with_null_check = true:suggestion | ||||
| # Null-checking preferences  | ||||
| csharp_style_throw_expression = true:suggestion | ||||
| csharp_style_conditional_delegate_call = true:suggestion | ||||
| # Modifier preferences  | ||||
| csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion | ||||
| # Expression-level preferences  | ||||
| csharp_prefer_braces = true:silent | ||||
| csharp_style_deconstructed_variable_declaration = true:suggestion | ||||
| csharp_prefer_simple_default_expression = true:suggestion | ||||
| csharp_style_pattern_local_over_anonymous_function = true:suggestion | ||||
| csharp_style_inlined_variable_declaration = true:suggestion | ||||
| ###############################  | ||||
| # C# Formatting Rules         #  | ||||
| ###############################  | ||||
| # New line preferences  | ||||
| csharp_new_line_before_open_brace = all | ||||
| csharp_new_line_before_else = true | ||||
| csharp_new_line_before_catch = true | ||||
| csharp_new_line_before_finally = true | ||||
| csharp_new_line_before_members_in_object_initializers = true | ||||
| csharp_new_line_before_members_in_anonymous_types = true | ||||
| csharp_new_line_between_query_expression_clauses = true | ||||
| # Indentation preferences  | ||||
| csharp_indent_case_contents = true | ||||
| csharp_indent_switch_labels = true | ||||
| csharp_indent_labels = flush_left | ||||
| # Space preferences  | ||||
| csharp_space_after_cast = false | ||||
| csharp_space_after_keywords_in_control_flow_statements = true | ||||
| csharp_space_between_method_call_parameter_list_parentheses = false | ||||
| csharp_space_between_method_declaration_parameter_list_parentheses = false | ||||
| csharp_space_between_parentheses = false | ||||
| csharp_space_before_colon_in_inheritance_clause = true | ||||
| csharp_space_after_colon_in_inheritance_clause = true | ||||
| csharp_space_around_binary_operators = before_and_after | ||||
| csharp_space_between_method_declaration_empty_parameter_list_parentheses = false | ||||
| csharp_space_between_method_call_name_and_opening_parenthesis = false | ||||
| csharp_space_between_method_call_empty_parameter_list_parentheses = false | ||||
| # Wrapping preferences  | ||||
| csharp_preserve_single_line_statements = true | ||||
| csharp_preserve_single_line_blocks = true | ||||
| ###############################  | ||||
| # VB Coding Conventions       #  | ||||
| ###############################  | ||||
| [*.vb] | ||||
| # Modifier preferences  | ||||
| visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion | ||||
| [*.cs] | ||||
| # Add file header | ||||
| file_header_template = Copyright (c) Argo Zhang (argo@163.com). All rights reserved.\nLicensed under the Apache License, Version 2.0. See License.txt in the project root for license information.\nWebsite: https://www.blazor.zone or https://argozhang.github.io/ | ||||
| csharp_style_namespace_declarations = file_scoped:suggestion | ||||
| csharp_style_expression_bodied_local_functions = true:silent | ||||
| csharp_using_directive_placement = outside_namespace:silent | ||||
| csharp_prefer_simple_using_statement = true:suggestion | ||||
| csharp_style_prefer_method_group_conversion = true:silent | ||||
| csharp_style_prefer_top_level_statements = true:silent | ||||
| csharp_style_prefer_primary_constructors = true:suggestion | ||||
| csharp_style_expression_bodied_lambdas = true:silent | ||||
| csharp_style_prefer_null_check_over_type_check = true:suggestion | ||||
| csharp_style_prefer_local_over_anonymous_function = true:suggestion | ||||
| csharp_style_prefer_index_operator = true:suggestion | ||||
| csharp_style_prefer_range_operator = true:suggestion | ||||
| csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion | ||||
| csharp_style_prefer_utf8_string_literals = true:suggestion | ||||
| csharp_style_prefer_tuple_swap = true:suggestion | ||||
| csharp_style_unused_value_expression_statement_preference = discard_variable:silent | ||||
| csharp_style_unused_value_assignment_preference = discard_variable:suggestion | ||||
| csharp_style_prefer_readonly_struct_member = true:suggestion | ||||
| csharp_style_prefer_readonly_struct = true:suggestion | ||||
| csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent | ||||
| csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent | ||||
| csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent | ||||
| csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent | ||||
| csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent | ||||
| csharp_style_prefer_switch_expression = true:suggestion | ||||
| csharp_style_prefer_pattern_matching = true:silent | ||||
| csharp_style_prefer_not_pattern = true:suggestion | ||||
| csharp_style_prefer_extended_property_pattern = true:suggestion | ||||
|   | ||||
							
								
								
									
										13
									
								
								src/Delete .vs .bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,13 @@ | ||||
| @echo off | ||||
| chcp 65001 | ||||
| setlocal enabledelayedexpansion | ||||
|  | ||||
| set "folder=%~dp0" | ||||
| attrib -s -h "%folder%\.vs" >nul 2>&1 | ||||
| if exist "%folder%\.vs" ( | ||||
|     rd /s /q "%folder%\.vs" | ||||
|     echo 删除了.vs文件夹:%folder%\.vs | ||||
| ) | ||||
|  | ||||
| echo 删除完成! | ||||
| pause | ||||
							
								
								
									
										19
									
								
								src/Delete bin Or obj.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | ||||
| @echo off | ||||
| chcp 65001 | ||||
| setlocal enabledelayedexpansion | ||||
|  | ||||
| set "folder=%~dp0" | ||||
| for /r "%folder%" /d %%i in (*) do ( | ||||
|     set "dirname=%%~nxi" | ||||
|     if /I "!dirname!"=="bin" ( | ||||
|         rd /s /q "%%i" | ||||
|         echo 删除了名称为"bin"的文件夹:%%i | ||||
|     ) | ||||
|         if /I "!dirname!"=="obj" ( | ||||
|         rd /s /q "%%i" | ||||
|         echo 删除了名称为"obj"的文件夹:%%i | ||||
|     ) | ||||
| ) | ||||
|  | ||||
| echo 删除完成! | ||||
| pause | ||||
							
								
								
									
										10
									
								
								src/Delete nupkgs.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | ||||
| @echo off | ||||
| chcp 65001 | ||||
| setlocal enabledelayedexpansion | ||||
|  | ||||
| set "folder=%~dp0/nupkgs" | ||||
|  rd /s /q "%folder%" | ||||
| echo 删除了名称为"nupkgs"的文件夹 | ||||
|  | ||||
| echo 删除完成! | ||||
| pause | ||||
| @@ -1,26 +1,26 @@ | ||||
| <Project> | ||||
| 	<PropertyGroup> | ||||
| 		<NoWarn>CS8618;CS8625;CS8600;CS8601;CS8604;CS8714;CS8602;CS8603;CS8619;CS8621</NoWarn> | ||||
| 		<TargetFrameworks>net6.0;</TargetFrameworks> | ||||
| 		<LangVersion>11.0</LangVersion> | ||||
| 		<ImplicitUsings>enable</ImplicitUsings> | ||||
| 		<Nullable>enable</Nullable> | ||||
| 		<Version>5.0.2.0</Version> | ||||
| 		<Authors>Diego</Authors> | ||||
| 		<Company>Diego</Company> | ||||
| 		<Product>Diego</Product> | ||||
| 		<Copyright>版权所有 © 2023-present Diego</Copyright> | ||||
| 		<RepositoryUrl>https://gitee.com/diego2098/ThingsGateway</RepositoryUrl> | ||||
| 		<RepositoryType>Gitee</RepositoryType> | ||||
| 		<GenerateResxSourceIncludeDefaultValues>true</GenerateResxSourceIncludeDefaultValues> | ||||
| 	</PropertyGroup> | ||||
| 	<PropertyGroup Condition="'$(SolutionName)'=='ThingsGateway - Admin'"> | ||||
| 		<DefineConstants>Admin</DefineConstants> | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<None Include="$(SolutionDir)..\README.md" Pack="true" PackagePath="\" /> | ||||
| 		<None Include="$(SolutionDir)..\LICENSE" Pack="true" PackagePath="\" /> | ||||
| 		<None Include="$(SolutionDir)..\icon.png" Pack="true" PackagePath="\" /> | ||||
| 		<None Include="$(SolutionDir)Directory.Build.props" Pack="true" PackagePath="\" /> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
|   <PropertyGroup> | ||||
|     <NoWarn>CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;</NoWarn> | ||||
|     <TargetFrameworks>net6.0;</TargetFrameworks> | ||||
|     <LangVersion>12.0</LangVersion> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <Version>6.0.0.9</Version> | ||||
|     <Authors>Diego</Authors> | ||||
|     <Company>Diego</Company> | ||||
|     <Product>Diego</Product> | ||||
|     <Copyright>版权所有 © 2023-present Diego</Copyright> | ||||
|     <RepositoryUrl>https://gitee.com/diego2098/ThingsGateway</RepositoryUrl> | ||||
|     <RepositoryType>Gitee</RepositoryType> | ||||
|     <GenerateResxSourceIncludeDefaultValues>true</GenerateResxSourceIncludeDefaultValues> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|  | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <None Include="$(SolutionDir)..\README.md" Pack="true" PackagePath="\" /> | ||||
|     <None Include="$(SolutionDir)..\LICENSE" Pack="true" PackagePath="\" /> | ||||
|     <None Include="$(SolutionDir)..\icon.png" Pack="true" PackagePath="\" /> | ||||
|     <None Include="$(SolutionDir)Directory.Build.props" Pack="true" PackagePath="\" /> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
|   | ||||
| @@ -1,25 +1,14 @@ | ||||
| <Project> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net45;netstandard2.0;net6.0;</TargetFrameworks> | ||||
| 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||
| 		<GeneratePackageOnBuild>true</GeneratePackageOnBuild> | ||||
| 		<IncludeSymbols>true</IncludeSymbols> | ||||
| 		<SymbolPackageFormat>snupkg</SymbolPackageFormat> | ||||
| 		<PackageOutputPath>$(SolutionDir)nupkgs</PackageOutputPath> | ||||
| 		<PackageVersion>$(Version)</PackageVersion> | ||||
| 		<PackageTags>ThingsGateway;Diego;Blazor;设备采集;边缘网关;物联网</PackageTags> | ||||
| 		<PackageProjectUrl>https://gitee.com/diego2098/ThingsGateway</PackageProjectUrl> | ||||
| 		<PackageIcon>icon.png</PackageIcon> | ||||
| 		<PackageReadmeFile>README.md</PackageReadmeFile> | ||||
| 		<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> | ||||
| 		<OutputPath></OutputPath> | ||||
| 	</PropertyGroup> | ||||
| 	<PropertyGroup Condition="'$(Configuration)' == 'Release'"> | ||||
| 		<DebugSymbols>True</DebugSymbols> | ||||
| 		<DebugType>Embedded</DebugType> | ||||
| 		<EmbedAllSources>True</EmbedAllSources> | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<None Include="$(SolutionDir)Foundation.props" Pack="true" PackagePath="\" /> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
|   <PropertyGroup> | ||||
|     <TargetFrameworks>net462;netstandard2.0;net6.0;</TargetFrameworks> | ||||
|   </PropertyGroup> | ||||
|   <ItemGroup> | ||||
|     <Content Remove="Locales\*.json" /> | ||||
|     <EmbeddedResource Include="Locales\*.json"> | ||||
|       <CopyToOutputDirectory>Never</CopyToOutputDirectory> | ||||
|     </EmbeddedResource> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <None Include="$(SolutionDir)Foundation.props" Pack="true" PackagePath="\" /> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| <Project> | ||||
| 	<PropertyGroup> | ||||
|  | ||||
| 	<PropertyGroup Condition="'$(Configuration)' == 'Release'"> | ||||
|  | ||||
| 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||
| 		<GeneratePackageOnBuild>true</GeneratePackageOnBuild> | ||||
| 		<IncludeSymbols>true</IncludeSymbols> | ||||
| 		<SymbolPackageFormat>snupkg</SymbolPackageFormat> | ||||
| 		<!--<IncludeSymbols>true</IncludeSymbols> | ||||
| 		<SymbolPackageFormat>snupkg</SymbolPackageFormat>--> | ||||
| 		<PackageOutputPath>$(SolutionDir)nupkgs</PackageOutputPath> | ||||
| 		<PackageVersion>$(Version)</PackageVersion> | ||||
| 		<PackageTags>ThingsGateway;Diego;Blazor;设备采集;边缘网关;物联网</PackageTags> | ||||
| @@ -11,9 +13,11 @@ | ||||
| 		<PackageIcon>icon.png</PackageIcon> | ||||
| 		<PackageReadmeFile>README.md</PackageReadmeFile> | ||||
| 		<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> | ||||
| 		<OutputPath></OutputPath> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 		<DebugSymbols>True</DebugSymbols> | ||||
| 		<DebugType>Embedded</DebugType> | ||||
| 		<EmbedAllSources>True</EmbedAllSources> | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<None Include="$(SolutionDir)PackNuget.props" Pack="true" PackagePath="\" /> | ||||
| 	</ItemGroup> | ||||
|   | ||||
| @@ -6,12 +6,17 @@ | ||||
| 	<ItemGroup> | ||||
| 		<None Include="$(SolutionDir)Plugin.props" Pack="true" PackagePath="\" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <Content Remove="Locales\*.json" /> | ||||
|     <EmbeddedResource Include="Locales\*.json"> | ||||
|       <CopyToOutputDirectory>Never</CopyToOutputDirectory> | ||||
|     </EmbeddedResource> | ||||
|   </ItemGroup> | ||||
| 	<ItemGroup> | ||||
|  | ||||
| 		<ProjectReference Include="$(SolutionDir)ThingsGateway.Gateway.Blazor\ThingsGateway.Gateway.Blazor.csproj"> | ||||
| 		<ProjectReference Include="$(SolutionDir)ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj"> | ||||
| 			<Private>false</Private> | ||||
| 			<ExcludeAssets>runtime</ExcludeAssets> | ||||
| 		</ProjectReference> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
| </Project> | ||||
|   | ||||
| @@ -1,35 +1,31 @@ | ||||
|  | ||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
| # Visual Studio Version 17 | ||||
| VisualStudioVersion = 17.8.34322.80 | ||||
| VisualStudioVersion = 17.9.34622.214 | ||||
| MinimumVisualStudioVersion = 10.0.40219.1 | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3CBF3D90-92F4-4932-8E63-19BD344B19D6}" | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Server", "ThingsGateway.Server\ThingsGateway.Server.csproj", "{BDC88C7C-D37B-44FB-9E84-2619F8B8B007}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Razor", "ThingsGateway.Razor\ThingsGateway.Razor.csproj", "{D3E8D660-AD12-4C46-871F-144D9C83ED2B}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Core", "ThingsGateway.Core\ThingsGateway.Core.csproj", "{2904D604-40E9-459C-81E4-2B55FFB27706}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "others", "others", "{2C6B6E2F-76B9-47E1-B3BB-A76595275F2A}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		.editorconfig = .editorconfig | ||||
| 		Delete .vs .bat = Delete .vs .bat | ||||
| 		Delete Bin And Obj.bat = Delete Bin And Obj.bat | ||||
| 		Directory.Build.props = Directory.Build.props | ||||
| 		Plugin.props = Plugin.props | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Cache", "ThingsGateway.Cache\ThingsGateway.Cache.csproj", "{2D61A1A2-60EB-4B77-BE5B-DA4D6AE0D68D}" | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Razor", "ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj", "{81C3B3AF-9DA6-404B-A20A-31F2FC1CE832}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Components", "ThingsGateway.Components\ThingsGateway.Components.csproj", "{D86209BA-BB60-40B1-AD55-1E8E08CCADD8}" | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Application", "ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj", "{5EE2DAEF-05DF-4590-B1FA-42917D859F73}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Core", "ThingsGateway.Core\ThingsGateway.Core.csproj", "{62AF3C40-180D-4C4F-AF35-C1FD2346DA0E}" | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.NewLife.X", "ThingsGateway.NewLife.X\ThingsGateway.NewLife.X.csproj", "{1A04C297-BA57-45F1-8D74-D1DD8FCE83B9}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Core", "ThingsGateway.Admin.Core\ThingsGateway.Admin.Core.csproj", "{E43C28BD-E208-424E-8A67-75BE712DC7B4}" | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "admin", "admin", "{1A13C5CC-69C9-42D9-8046-96DDECA0467A}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Application", "ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj", "{C7B0B47B-4AF5-452A-8CB7-B709DC694FAA}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.ApiController", "ThingsGateway.Admin.ApiController\ThingsGateway.Admin.ApiController.csproj", "{D8A1033A-FBE8-4F51-A7B6-282891D69AAF}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Blazor", "ThingsGateway.Admin.Blazor\ThingsGateway.Admin.Blazor.csproj", "{E2F43E54-D2B6-4789-86BA-53395CA78A44}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Core", "ThingsGateway.Web.Core\ThingsGateway.Web.Core.csproj", "{6AE58F3E-E996-448F-994A-C054EA01EC96}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Entry", "ThingsGateway.Web.Entry\ThingsGateway.Web.Entry.csproj", "{2733F3EB-8854-43B7-9DF4-859EA34B35CB}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "admin", "admin", "{F315F04B-CC19-4ACC-8544-ACFE27D1EF2B}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{BF1ED6B2-7779-4FE6-8332-85C6713F42BB}" | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{3D3C6EB5-8E66-4B4F-A2F6-8C66673F3116}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| @@ -37,57 +33,42 @@ Global | ||||
| 		Release|Any CPU = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
| 		{2D61A1A2-60EB-4B77-BE5B-DA4D6AE0D68D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{2D61A1A2-60EB-4B77-BE5B-DA4D6AE0D68D}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{2D61A1A2-60EB-4B77-BE5B-DA4D6AE0D68D}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{2D61A1A2-60EB-4B77-BE5B-DA4D6AE0D68D}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{D86209BA-BB60-40B1-AD55-1E8E08CCADD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{D86209BA-BB60-40B1-AD55-1E8E08CCADD8}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{D86209BA-BB60-40B1-AD55-1E8E08CCADD8}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{D86209BA-BB60-40B1-AD55-1E8E08CCADD8}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{62AF3C40-180D-4C4F-AF35-C1FD2346DA0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{62AF3C40-180D-4C4F-AF35-C1FD2346DA0E}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{62AF3C40-180D-4C4F-AF35-C1FD2346DA0E}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{62AF3C40-180D-4C4F-AF35-C1FD2346DA0E}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{E43C28BD-E208-424E-8A67-75BE712DC7B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{E43C28BD-E208-424E-8A67-75BE712DC7B4}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{E43C28BD-E208-424E-8A67-75BE712DC7B4}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{E43C28BD-E208-424E-8A67-75BE712DC7B4}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{C7B0B47B-4AF5-452A-8CB7-B709DC694FAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{C7B0B47B-4AF5-452A-8CB7-B709DC694FAA}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{C7B0B47B-4AF5-452A-8CB7-B709DC694FAA}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{C7B0B47B-4AF5-452A-8CB7-B709DC694FAA}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{D8A1033A-FBE8-4F51-A7B6-282891D69AAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{D8A1033A-FBE8-4F51-A7B6-282891D69AAF}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{D8A1033A-FBE8-4F51-A7B6-282891D69AAF}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{D8A1033A-FBE8-4F51-A7B6-282891D69AAF}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{E2F43E54-D2B6-4789-86BA-53395CA78A44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{E2F43E54-D2B6-4789-86BA-53395CA78A44}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{E2F43E54-D2B6-4789-86BA-53395CA78A44}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{E2F43E54-D2B6-4789-86BA-53395CA78A44}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{6AE58F3E-E996-448F-994A-C054EA01EC96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{6AE58F3E-E996-448F-994A-C054EA01EC96}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{6AE58F3E-E996-448F-994A-C054EA01EC96}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{6AE58F3E-E996-448F-994A-C054EA01EC96}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{2733F3EB-8854-43B7-9DF4-859EA34B35CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{2733F3EB-8854-43B7-9DF4-859EA34B35CB}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{2733F3EB-8854-43B7-9DF4-859EA34B35CB}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{2733F3EB-8854-43B7-9DF4-859EA34B35CB}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{BDC88C7C-D37B-44FB-9E84-2619F8B8B007}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{BDC88C7C-D37B-44FB-9E84-2619F8B8B007}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{BDC88C7C-D37B-44FB-9E84-2619F8B8B007}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{BDC88C7C-D37B-44FB-9E84-2619F8B8B007}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{D3E8D660-AD12-4C46-871F-144D9C83ED2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{D3E8D660-AD12-4C46-871F-144D9C83ED2B}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{D3E8D660-AD12-4C46-871F-144D9C83ED2B}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{D3E8D660-AD12-4C46-871F-144D9C83ED2B}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{2904D604-40E9-459C-81E4-2B55FFB27706}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{2904D604-40E9-459C-81E4-2B55FFB27706}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{2904D604-40E9-459C-81E4-2B55FFB27706}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{2904D604-40E9-459C-81E4-2B55FFB27706}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{81C3B3AF-9DA6-404B-A20A-31F2FC1CE832}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{81C3B3AF-9DA6-404B-A20A-31F2FC1CE832}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{81C3B3AF-9DA6-404B-A20A-31F2FC1CE832}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{81C3B3AF-9DA6-404B-A20A-31F2FC1CE832}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{5EE2DAEF-05DF-4590-B1FA-42917D859F73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{5EE2DAEF-05DF-4590-B1FA-42917D859F73}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{5EE2DAEF-05DF-4590-B1FA-42917D859F73}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{5EE2DAEF-05DF-4590-B1FA-42917D859F73}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{1A04C297-BA57-45F1-8D74-D1DD8FCE83B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{1A04C297-BA57-45F1-8D74-D1DD8FCE83B9}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{1A04C297-BA57-45F1-8D74-D1DD8FCE83B9}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{1A04C297-BA57-45F1-8D74-D1DD8FCE83B9}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(NestedProjects) = preSolution | ||||
| 		{2D61A1A2-60EB-4B77-BE5B-DA4D6AE0D68D} = {BF1ED6B2-7779-4FE6-8332-85C6713F42BB} | ||||
| 		{D86209BA-BB60-40B1-AD55-1E8E08CCADD8} = {BF1ED6B2-7779-4FE6-8332-85C6713F42BB} | ||||
| 		{62AF3C40-180D-4C4F-AF35-C1FD2346DA0E} = {BF1ED6B2-7779-4FE6-8332-85C6713F42BB} | ||||
| 		{E43C28BD-E208-424E-8A67-75BE712DC7B4} = {F315F04B-CC19-4ACC-8544-ACFE27D1EF2B} | ||||
| 		{C7B0B47B-4AF5-452A-8CB7-B709DC694FAA} = {F315F04B-CC19-4ACC-8544-ACFE27D1EF2B} | ||||
| 		{D8A1033A-FBE8-4F51-A7B6-282891D69AAF} = {F315F04B-CC19-4ACC-8544-ACFE27D1EF2B} | ||||
| 		{E2F43E54-D2B6-4789-86BA-53395CA78A44} = {F315F04B-CC19-4ACC-8544-ACFE27D1EF2B} | ||||
| 		{D3E8D660-AD12-4C46-871F-144D9C83ED2B} = {3D3C6EB5-8E66-4B4F-A2F6-8C66673F3116} | ||||
| 		{2904D604-40E9-459C-81E4-2B55FFB27706} = {3D3C6EB5-8E66-4B4F-A2F6-8C66673F3116} | ||||
| 		{81C3B3AF-9DA6-404B-A20A-31F2FC1CE832} = {1A13C5CC-69C9-42D9-8046-96DDECA0467A} | ||||
| 		{5EE2DAEF-05DF-4590-B1FA-42917D859F73} = {1A13C5CC-69C9-42D9-8046-96DDECA0467A} | ||||
| 		{1A04C297-BA57-45F1-8D74-D1DD8FCE83B9} = {3D3C6EB5-8E66-4B4F-A2F6-8C66673F3116} | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		RESX_NeutralResourcesLanguage = zh-Hans | ||||
| 		SolutionGuid = {789BA852-9F20-4421-A555-E665A12587D1} | ||||
| 		SolutionGuid = {199B1B96-4F56-4828-9531-813BA02DB282} | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
|   | ||||
| @@ -1,64 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
|  | ||||
| using System.ComponentModel; | ||||
|  | ||||
| using ThingsGateway.Admin.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.ApiController; | ||||
|  | ||||
| /// <summary> | ||||
| /// 后台登录控制器 | ||||
| /// </summary> | ||||
| [ApiDescriptionSettings(CateGoryConst.ThingsGatewayAdmin, Order = 200)] | ||||
| [Route("auth")] | ||||
| [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.LoginOut)] | ||||
|     [Authorize] | ||||
|     [IgnoreRolePermission] | ||||
|     public async Task LogoutAsync(LoginOutIput input) | ||||
|     { | ||||
|         await _authService.LoginOutAsync(input.VerificatId); | ||||
|     } | ||||
| } | ||||
| @@ -1,75 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
|  | ||||
| namespace ThingsGateway.Admin.ApiController; | ||||
|  | ||||
| /// <summary> | ||||
| /// 导出文件 | ||||
| /// </summary> | ||||
| [ApiDescriptionSettings(CateGoryConst.ThingsGatewayAdmin, Order = 200)] | ||||
| [Route("export")] | ||||
| [LoggingMonitor] | ||||
| public class ExportController : ControllerBase | ||||
| { | ||||
|     private readonly IOperateLogService _operateLogService; | ||||
|     private readonly IVisitLogService _visitLogService; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="ExportController"/> | ||||
|     /// </summary> | ||||
|     public ExportController( | ||||
|         IOperateLogService operateLogService, | ||||
|         IVisitLogService visitLogService | ||||
|         ) | ||||
|     { | ||||
|         _operateLogService = operateLogService; | ||||
|         _visitLogService = visitLogService; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 下载操作日志 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     [HttpGet("operateLog")] | ||||
|     public async Task<IActionResult> DownloadOperateLogAsync([FromQuery] OperateLogInput input) | ||||
|     { | ||||
|         if (input.All) | ||||
|         { | ||||
|             var fileStreamResult = await _operateLogService.ExportFileAsync(); | ||||
|             return fileStreamResult; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             var fileStreamResult = await _operateLogService.ExportFileAsync(input); | ||||
|             return fileStreamResult; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 下载访问日志 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     [HttpGet("visitLog")] | ||||
|     public async Task<IActionResult> DownloadVisitLogAsync([FromQuery] VisitLogInput input) | ||||
|     { | ||||
|         if (input.All) | ||||
|         { | ||||
|             var fileStreamResult = await _visitLogService.ExportFileAsync(); | ||||
|             return fileStreamResult; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             var fileStreamResult = await _visitLogService.ExportFileAsync(input); | ||||
|             return fileStreamResult; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| global using Furion.DynamicApiController; | ||||
|  | ||||
| global using System; | ||||
| global using System.Threading.Tasks; | ||||
|  | ||||
| global using ThingsGateway.Admin.Application; | ||||
| @@ -1,12 +0,0 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<DocumentationFile>$(MSBuildProjectName).xml</DocumentationFile> | ||||
| 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||
| 	</PropertyGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
| 	  <ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| </Project> | ||||
| @@ -1,54 +0,0 @@ | ||||
| <?xml version="1.0"?> | ||||
| <doc> | ||||
|     <assembly> | ||||
|         <name>ThingsGateway.Admin.ApiController</name> | ||||
|     </assembly> | ||||
|     <members> | ||||
|         <member name="T:ThingsGateway.Admin.ApiController.AuthController"> | ||||
|             <summary> | ||||
|             后台登录控制器 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.ApiController.AuthController.#ctor(ThingsGateway.Admin.Application.IAuthService)"> | ||||
|             <summary> | ||||
|             <inheritdoc cref="T:ThingsGateway.Admin.ApiController.AuthController"/> | ||||
|             </summary> | ||||
|             <param name="authService"></param> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.ApiController.AuthController.LoginAsync(ThingsGateway.Admin.Application.LoginInput)"> | ||||
|             <summary> | ||||
|             后台登录 | ||||
|             </summary> | ||||
|             <param name="input"></param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.ApiController.AuthController.LogoutAsync(ThingsGateway.Admin.Application.LoginOutIput)"> | ||||
|             <summary> | ||||
|             后台登出 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.ApiController.ExportController"> | ||||
|             <summary> | ||||
|             导出文件 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.ApiController.ExportController.#ctor(ThingsGateway.Admin.Application.IOperateLogService,ThingsGateway.Admin.Application.IVisitLogService)"> | ||||
|             <summary> | ||||
|             <inheritdoc cref="T:ThingsGateway.Admin.ApiController.ExportController"/> | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.ApiController.ExportController.DownloadOperateLogAsync(ThingsGateway.Admin.Application.OperateLogInput)"> | ||||
|             <summary> | ||||
|             下载操作日志 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.ApiController.ExportController.DownloadVisitLogAsync(ThingsGateway.Admin.Application.VisitLogInput)"> | ||||
|             <summary> | ||||
|             下载访问日志 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|     </members> | ||||
| </doc> | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,37 +9,160 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Hosting; | ||||
|  | ||||
| using Rougamo; | ||||
| using Rougamo.Context; | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
|  | ||||
| using ThingsGateway.Core.Extension; | ||||
| using ThingsGateway.Core.Json.Extension; | ||||
|  | ||||
| using UAParser; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 操作事件说明特性 | ||||
| /// Aop拦截器 | ||||
| /// </summary> | ||||
| [AttributeUsage(AttributeTargets.Method)] | ||||
| public class OperDescAttribute : Attribute | ||||
| public class OperDescAttribute : MoAttribute | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 操作记录标识 | ||||
|     /// 日志消息队列(线程安全) | ||||
|     /// </summary> | ||||
|     /// <param name="description"></param> | ||||
|     /// <param name="catcategory"></param> | ||||
|     public OperDescAttribute(string description, string catcategory = CateGoryConst.Log_OPERATE) | ||||
|     private static readonly ConcurrentQueue<SysOperateLog> _logMessageQueue = new(); | ||||
|  | ||||
|     static OperDescAttribute() | ||||
|     { | ||||
|         Description = description; | ||||
|         Catcategory = catcategory; | ||||
|         // 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中 | ||||
|         Task.Factory.StartNew(ProcessQueue, TaskCreationOptions.LongRunning); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 分类 | ||||
|     /// </summary> | ||||
|     public string Catcategory { get; } | ||||
|     public OperDescAttribute(string description, bool isRecordPar = true, object localizerType = null) | ||||
|     { | ||||
|         Description = description; | ||||
|         IsRecordPar = isRecordPar; | ||||
|         LocalizerType = (Type)localizerType; | ||||
|     } | ||||
|  | ||||
|     public override AccessFlags Flags => AccessFlags.Public | AccessFlags.Method; | ||||
|     public override Feature Features => Feature.OnException | Feature.OnSuccess; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 说明 | ||||
|     /// 说明,需配置本地化json文件 | ||||
|     /// </summary> | ||||
|     public string Description { get; } | ||||
|  | ||||
|     public Type? LocalizerType { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 记录参数,默认true | ||||
|     /// 是否记录进出参数 | ||||
|     /// </summary> | ||||
|     public bool IsRecordPar { get; set; } = true; | ||||
| } | ||||
|     public bool IsRecordPar { get; } | ||||
|  | ||||
|     public override void OnSuccess(MethodContext context) | ||||
|     { | ||||
|         //插入操作日志 | ||||
|         SysOperateLog log = GetOperLog(LocalizerType, context); | ||||
|         WriteToQueue(log); | ||||
|     } | ||||
|  | ||||
|     public override void OnException(MethodContext context) | ||||
|     { | ||||
|         //插入异常日志 | ||||
|         SysOperateLog log = GetOperLog(LocalizerType, context); | ||||
|  | ||||
|         log.Category = LogCateGoryEnum.Exception;//操作类型为异常 | ||||
|         log.ExeStatus = false;//操作状态为失败 | ||||
|         if (context.Exception is UserFriendlyException exception) | ||||
|             log.ExeMessage = exception?.Message; | ||||
|         else | ||||
|             log.ExeMessage = context.Exception?.ToString(); | ||||
|  | ||||
|         WriteToQueue(log); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将日志消息写入数据库中 | ||||
|     /// </summary> | ||||
|     private static async Task ProcessQueue() | ||||
|     { | ||||
|         var db = DbContext.Db.GetConnectionScopeWithAttr<SysOperateLog>().CopyNew(); | ||||
|         var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!; | ||||
|         while (!(appLifetime.ApplicationStopping.IsCancellationRequested || appLifetime.ApplicationStopped.IsCancellationRequested)) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 if (_logMessageQueue.Count > 0) | ||||
|                 { | ||||
|                     await db.InsertableWithAttr(_logMessageQueue.ToListWithDequeue()).ExecuteCommandAsync();//入库 | ||||
|                 } | ||||
|                 await Task.Delay(3000, appLifetime.ApplicationStopping).ConfigureAwait(false); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 Console.WriteLine(ex.ToString()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private SysOperateLog GetOperLog(Type? localizerType, MethodContext context) | ||||
|     { | ||||
|         var str = App.HttpContext?.Request?.Headers?.UserAgent; | ||||
|         var methodBase = context.Method; | ||||
|         ClientInfo? clientInfo = null; | ||||
|         if (str.HasValue) | ||||
|         { | ||||
|             clientInfo = Parser.GetDefault().Parse(str); | ||||
|         } | ||||
|         string? paramJson = null; | ||||
|         if (IsRecordPar) | ||||
|         { | ||||
|             var args = context.Arguments; | ||||
|             var parametersInfo = methodBase.GetParameters(); | ||||
|             var parametersDict = new Dictionary<string, object>(); | ||||
|  | ||||
|             for (int i = 0; i < parametersInfo.Length; i++) | ||||
|             { | ||||
|                 parametersDict[parametersInfo[i].Name!] = args[i]; | ||||
|             } | ||||
|             paramJson = parametersDict.ToSystemTextJsonString(); | ||||
|         } | ||||
|         var result = context.ReturnValue; | ||||
|         var resultJson = IsRecordPar ? result?.ToSystemTextJsonString() : null; | ||||
|         //操作日志表实体 | ||||
|         var log = new SysOperateLog | ||||
|         { | ||||
|             Name = (localizerType == null ? App.CreateLocalizerByType(typeof(OperDescAttribute)) : App.CreateLocalizerByType(localizerType))![Description], | ||||
|             Category = LogCateGoryEnum.Operate, | ||||
|             ExeStatus = true, | ||||
|             OpIp = App.HttpContext?.Connection?.RemoteIpAddress?.MapToIPv4()?.ToString(), | ||||
|             OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major, | ||||
|             OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major, | ||||
|             OpTime = DateTime.Now, | ||||
|             OpAccount = UserManager.UserAccount, | ||||
|             ReqUrl = null, | ||||
|             ReqMethod = "browser", | ||||
|             ResultJson = resultJson, | ||||
|             ClassName = methodBase.ReflectedType!.Name, | ||||
|             MethodName = methodBase.Name, | ||||
|             ParamJson = paramJson, | ||||
|             VerificatId = UserManager.VerificatId, | ||||
|         }; | ||||
|         return log; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将日志消息写入队列中等待后台任务出队写入数据库 | ||||
|     /// </summary> | ||||
|     /// <param name="logMsg">结构化日志消息</param> | ||||
|     private void WriteToQueue(SysOperateLog logMsg) | ||||
|     { | ||||
|         _logMessageQueue.Enqueue(logMsg); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,258 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using Furion.Reflection; | ||||
| using Furion.Reflection.Extensions; | ||||
|  | ||||
| using Microsoft.Extensions.Hosting; | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
| using System.Reflection; | ||||
| using System.Text; | ||||
|  | ||||
| using ThingsGateway.Core.Extension.ConcurrentQueue; | ||||
| using ThingsGateway.Core.Extension.Json; | ||||
|  | ||||
| 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> | ||||
|     public override object Invoke(MethodInfo method, object[] args) | ||||
|     { | ||||
|         var desc = method.GetActualCustomAttribute<OperDescAttribute>(Target); | ||||
|         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, true); | ||||
|         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, true); | ||||
|         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 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 = Parser.GetDefault().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.SUCCESS, | ||||
|             OpIp = App.HttpContext?.Connection?.RemoteIpAddress?.MapToIPv4().ToString(), | ||||
|             OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major, | ||||
|             OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major, | ||||
|             OpTime = DateTime.Now, | ||||
|             OpAccount = UserManager.UserAccount, | ||||
|             ReqUrl = "", | ||||
|             ReqMethod = LogConst.LOG_REQMETHOD, | ||||
|             ResultJson = resultJson, | ||||
|             ClassName = method.ReflectedType.Name, | ||||
|             MethodName = method.Name, | ||||
|             ParamJson = paramJson, | ||||
|             VerificatId = UserManager.VerificatId, | ||||
|         }; | ||||
|         //如果异常不为空 | ||||
|         if (exception != null) | ||||
|         { | ||||
|             log.Category = CateGoryConst.Log_EXCEPTION;//操作类型为异常 | ||||
|             log.ExeStatus = LogConst.FAIL;//操作状态为失败 | ||||
|             log.ExeMessage = exception.Source + ":" + exception.Message + Environment.NewLine + exception.StackTrace; | ||||
|         } | ||||
|         WriteToQueue(log); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 日志消息队列(线程安全) | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentQueue<SysOperateLog> _logMessageQueue = new(); | ||||
|  | ||||
|     static OperDispatchProxy() | ||||
|     { | ||||
|         // 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中 | ||||
|         Task.Factory.StartNew(ProcessQueue, TaskCreationOptions.LongRunning); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将日志消息写入队列中等待后台任务出队写入数据库 | ||||
|     /// </summary> | ||||
|     /// <param name="logMsg">结构化日志消息</param> | ||||
|     private void WriteToQueue(SysOperateLog logMsg) | ||||
|     { | ||||
|         _logMessageQueue.Enqueue(logMsg); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将日志消息写入数据库中 | ||||
|     /// </summary> | ||||
|     private static async Task ProcessQueue() | ||||
|     { | ||||
|         var db = DbContext.Db.CopyNew(); | ||||
|         var appLifetime = App.GetService<IHostApplicationLifetime>(); | ||||
|         while (!(appLifetime.ApplicationStopping.IsCancellationRequested || appLifetime.ApplicationStopped.IsCancellationRequested)) | ||||
|         { | ||||
|             if (_logMessageQueue.Count > 0) | ||||
|             { | ||||
|                 await db.InsertableWithAttr(_logMessageQueue.ToListWithDequeue()).ExecuteCommandAsync();//入库 | ||||
|             } | ||||
|             await Task.Delay(3000); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,7 +9,10 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 忽略Excel导入导出 | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,7 +9,10 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 需要角色授权权限 | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,11 +9,15 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 种子数据忽略新增 | ||||
| /// </summary> | ||||
| [AttributeUsage(AttributeTargets.Class)] | ||||
| public class IgnoreSeedDataAddAttribute : Attribute | ||||
| { | ||||
| } | ||||
| @@ -20,6 +25,7 @@ public class IgnoreSeedDataAddAttribute : Attribute | ||||
| /// <summary> | ||||
| /// 种子数据忽略修改 | ||||
| /// </summary> | ||||
| [AttributeUsage(AttributeTargets.Class)] | ||||
| public class IgnoreSeedDataUpdateAttribute : Attribute | ||||
| { | ||||
| } | ||||
| @@ -27,6 +33,7 @@ public class IgnoreSeedDataUpdateAttribute : Attribute | ||||
| /// <summary> | ||||
| /// 忽略初始化表 | ||||
| /// </summary> | ||||
| [AttributeUsage(AttributeTargets.Class)] | ||||
| public class IgnoreInitTableAttribute : Attribute | ||||
| { | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,7 +9,10 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 管理员才能访问 | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,9 +9,12 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 最小值校验 | ||||
| @@ -21,18 +25,12 @@ public class MinValueAttribute : ValidationAttribute | ||||
|     /// 最小值 | ||||
|     /// </summary> | ||||
|     /// <param name="value"></param> | ||||
|     public MinValueAttribute(double value) | ||||
|     public MinValueAttribute(UInt64 value) | ||||
|     { | ||||
|         MinValue = value; | ||||
|     } | ||||
| 
 | ||||
|     private double MinValue { get; set; } | ||||
| 
 | ||||
|     /// <inheritdoc/> | ||||
|     public override string FormatErrorMessage(string name) | ||||
|     { | ||||
|         return base.FormatErrorMessage(name); | ||||
|     } | ||||
|     private UInt64 MinValue { get; set; } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 最小值校验 | ||||
| @@ -43,10 +41,10 @@ public class MinValueAttribute : ValidationAttribute | ||||
|     { | ||||
|         if (value is null) | ||||
|         { | ||||
|             return true; | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         var input = Convert.ToDouble(value); | ||||
|         return input > MinValue; | ||||
|         var input = Convert.ToUInt64(value); | ||||
|         return input >= MinValue; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										676
									
								
								src/ThingsGateway.Admin.Application/Common/ConcurrentList.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,676 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
| using System.Collections; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application.ConcurrentList; | ||||
|  | ||||
| /// <summary> | ||||
| /// 线程安全的List,其基本操作和List一致。 | ||||
| /// </summary> | ||||
| /// <typeparam name="T"></typeparam> | ||||
| public class ConcurrentList<T> : IList<T>, IReadOnlyList<T> | ||||
| { | ||||
|     private readonly List<T> m_list; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="collection"></param> | ||||
|     public ConcurrentList(IEnumerable<T> collection) | ||||
|     { | ||||
|         this.m_list = new List<T>(collection); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public ConcurrentList() | ||||
|     { | ||||
|         this.m_list = new List<T>(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="capacity"></param> | ||||
|     public ConcurrentList(int capacity) | ||||
|     { | ||||
|         this.m_list = new List<T>(capacity); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 元素数量 | ||||
|     /// </summary> | ||||
|     public int Count | ||||
|     { | ||||
|         get | ||||
|         { | ||||
|             lock (((ICollection)this.m_list).SyncRoot) | ||||
|             { | ||||
|                 return this.m_list.Count; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否为只读 | ||||
|     /// </summary> | ||||
|     public bool IsReadOnly => false; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取索引元素 | ||||
|     /// </summary> | ||||
|     /// <param name="index"></param> | ||||
|     /// <returns></returns> | ||||
|     public T this[int index] | ||||
|     { | ||||
|         get | ||||
|         { | ||||
|             lock (((ICollection)this.m_list).SyncRoot) | ||||
|             { | ||||
|                 return this.m_list[index]; | ||||
|             } | ||||
|         } | ||||
|         set | ||||
|         { | ||||
|             lock (((ICollection)this.m_list).SyncRoot) | ||||
|             { | ||||
|                 this.m_list[index] = value; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 添加元素 | ||||
|     /// </summary> | ||||
|     /// <param name="item"></param> | ||||
|     public void Add(T item) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.Add(item); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 清空所有元素 | ||||
|     /// </summary> | ||||
|     public void Clear() | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.Clear(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否包含某个元素 | ||||
|     /// </summary> | ||||
|     /// <param name="item"></param> | ||||
|     /// <returns></returns> | ||||
|     public bool Contains(T item) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.Contains(item); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 复制到 | ||||
|     /// </summary> | ||||
|     /// <param name="array"></param> | ||||
|     /// <param name="arrayIndex"></param> | ||||
|     public void CopyTo(T[] array, int arrayIndex) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.CopyTo(array, arrayIndex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 返回迭代器 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public IEnumerator<T> GetEnumerator() | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.ToList().GetEnumerator(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 返回迭代器组合 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     IEnumerator IEnumerable.GetEnumerator() | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.GetEnumerator(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 索引 | ||||
|     /// </summary> | ||||
|     /// <param name="item"></param> | ||||
|     /// <returns></returns> | ||||
|     public int IndexOf(T item) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.IndexOf(item); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 插入 | ||||
|     /// </summary> | ||||
|     /// <param name="index"></param> | ||||
|     /// <param name="item"></param> | ||||
|     public void Insert(int index, T item) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.Insert(index, item); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 移除元素 | ||||
|     /// </summary> | ||||
|     /// <param name="item"></param> | ||||
|     /// <returns></returns> | ||||
|     public bool Remove(T item) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.Remove(item); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 按索引移除 | ||||
|     /// </summary> | ||||
|     /// <param name="index"></param> | ||||
|     public void RemoveAt(int index) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             if (index < this.m_list.Count) | ||||
|             { | ||||
|                 this.m_list.RemoveAt(index); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取或设置容量 | ||||
|     /// </summary> | ||||
|     public int Capacity | ||||
|     { | ||||
|         get | ||||
|         { | ||||
|             lock (((ICollection)this.m_list).SyncRoot) | ||||
|             { | ||||
|                 return this.m_list.Capacity; | ||||
|             } | ||||
|         } | ||||
|         set | ||||
|         { | ||||
|             lock (((ICollection)this.m_list).SyncRoot) | ||||
|             { | ||||
|                 this.m_list.Capacity = value; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.AddRange(IEnumerable{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="collection"></param> | ||||
|     public void AddRange(IEnumerable<T> collection) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.AddRange(collection); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.BinarySearch(T)"/> | ||||
|     /// </summary> | ||||
|     /// <param name="item"></param> | ||||
|     /// <returns></returns> | ||||
|     public int BinarySearch(T item) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.BinarySearch(item); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.BinarySearch(T, IComparer{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="item"></param> | ||||
|     /// <param name="comparer"></param> | ||||
|     /// <returns></returns> | ||||
|     public int BinarySearch(T item, IComparer<T> comparer) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.BinarySearch(item, comparer); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.BinarySearch(int, int, T, IComparer{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="index"></param> | ||||
|     /// <param name="count"></param> | ||||
|     /// <param name="item"></param> | ||||
|     /// <param name="comparer"></param> | ||||
|     /// <returns></returns> | ||||
|     public int BinarySearch(int index, int count, T item, IComparer<T> comparer) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.BinarySearch(index, count, item, comparer); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.ConvertAll{TOutput}(Converter{T, TOutput})"/> | ||||
|     /// </summary> | ||||
|     /// <typeparam name="TOutput"></typeparam> | ||||
|     /// <param name="converter"></param> | ||||
|     /// <returns></returns> | ||||
|     public List<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.ConvertAll(converter); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.Find(Predicate{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="match"></param> | ||||
|     /// <returns></returns> | ||||
|     public T Find(Predicate<T> match) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.Find(match); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.FindAll(Predicate{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="match"></param> | ||||
|     /// <returns></returns> | ||||
|     public List<T> FindAll(Predicate<T> match) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.FindAll(match); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.FindIndex(int, int, Predicate{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="startIndex"></param> | ||||
|     /// <param name="count"></param> | ||||
|     /// <param name="match"></param> | ||||
|     /// <returns></returns> | ||||
|     public int FindIndex(int startIndex, int count, Predicate<T> match) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.FindIndex(startIndex, count, match); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.FindIndex(int, Predicate{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="startIndex"></param> | ||||
|     /// <param name="match"></param> | ||||
|     /// <returns></returns> | ||||
|     public int FindIndex(int startIndex, Predicate<T> match) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.FindIndex(startIndex, match); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.FindIndex(Predicate{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="match"></param> | ||||
|     /// <returns></returns> | ||||
|     public int FindIndex(Predicate<T> match) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.FindIndex(match); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.FindLast(Predicate{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="match"></param> | ||||
|     /// <returns></returns> | ||||
|     public T FindLast(Predicate<T> match) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.FindLast(match); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.FindLastIndex(int, int, Predicate{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="startIndex"></param> | ||||
|     /// <param name="count"></param> | ||||
|     /// <param name="match"></param> | ||||
|     /// <returns></returns> | ||||
|     public int FindLastIndex(int startIndex, int count, Predicate<T> match) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.FindLastIndex(startIndex, count, match); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.FindLastIndex(int, Predicate{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="startIndex"></param> | ||||
|     /// <param name="match"></param> | ||||
|     /// <returns></returns> | ||||
|     public int FindLastIndex(int startIndex, Predicate<T> match) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.FindLastIndex(startIndex, match); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.FindLastIndex(Predicate{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="match"></param> | ||||
|     /// <returns></returns> | ||||
|     public int FindLastIndex(Predicate<T> match) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.FindLastIndex(match); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.ForEach(Action{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="action"></param> | ||||
|     public void ForEach(Action<T> action) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.ForEach(action); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.GetRange(int, int)"/> | ||||
|     /// </summary> | ||||
|     /// <param name="index"></param> | ||||
|     /// <param name="count"></param> | ||||
|     /// <returns></returns> | ||||
|     public List<T> GetRange(int index, int count) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.GetRange(index, count); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.IndexOf(T, int)"/> | ||||
|     /// </summary> | ||||
|     /// <param name="item"></param> | ||||
|     /// <param name="index"></param> | ||||
|     /// <returns></returns> | ||||
|     public int IndexOf(T item, int index) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.IndexOf(item, index); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.IndexOf(T, int, int)"/> | ||||
|     /// </summary> | ||||
|     /// <param name="item"></param> | ||||
|     /// <param name="index"></param> | ||||
|     /// <param name="count"></param> | ||||
|     /// <returns></returns> | ||||
|     public int IndexOf(T item, int index, int count) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.IndexOf(item, index, count); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.InsertRange(int, IEnumerable{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="index"></param> | ||||
|     /// <param name="collection"></param> | ||||
|     public void InsertRange(int index, IEnumerable<T> collection) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.InsertRange(index, collection); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.LastIndexOf(T)"/> | ||||
|     /// </summary> | ||||
|     /// <param name="item"></param> | ||||
|     /// <returns></returns> | ||||
|     public int LastIndexOf(T item) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.IndexOf(item); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.LastIndexOf(T, int)"/> | ||||
|     /// </summary> | ||||
|     /// <param name="item"></param> | ||||
|     /// <param name="index"></param> | ||||
|     /// <returns></returns> | ||||
|     public int LastIndexOf(T item, int index) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.LastIndexOf(item, index); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.LastIndexOf(T, int, int)"/> | ||||
|     /// </summary> | ||||
|     /// <param name="item"></param> | ||||
|     /// <param name="index"></param> | ||||
|     /// <param name="count"></param> | ||||
|     /// <returns></returns> | ||||
|     public int LastIndexOf(T item, int index, int count) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.LastIndexOf(item, index, count); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.RemoveAll(Predicate{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="match"></param> | ||||
|     public void RemoveAll(Predicate<T> match) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.RemoveAll(match); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.RemoveRange(int, int)"/> | ||||
|     /// </summary> | ||||
|     /// <param name="index"></param> | ||||
|     /// <param name="count"></param> | ||||
|     public void RemoveRange(int index, int count) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.RemoveRange(index, count); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.Reverse()"/> | ||||
|     /// </summary> | ||||
|     public void Reverse() | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.Reverse(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.Reverse(int, int)"/> | ||||
|     /// </summary> | ||||
|     /// <param name="index"></param> | ||||
|     /// <param name="count"></param> | ||||
|     public void Reverse(int index, int count) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.Reverse(index, count); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.Sort()"/> | ||||
|     /// </summary> | ||||
|     public void Sort() | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.Sort(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.Sort(Comparison{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="comparison"></param> | ||||
|     public void Sort(Comparison<T> comparison) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.Sort(comparison); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.Sort(IComparer{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="comparer"></param> | ||||
|     public void Sort(IComparer<T> comparer) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.Sort(comparer); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.Sort(int, int, IComparer{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="index"></param> | ||||
|     /// <param name="count"></param> | ||||
|     /// <param name="comparer"></param> | ||||
|     public void Sort(int index, int count, IComparer<T> comparer) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.Sort(index, count, comparer); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.ToArray"/> | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public T[] ToArray() | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.ToArray(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.TrimExcess"/> | ||||
|     /// </summary> | ||||
|     public void TrimExcess() | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             this.m_list.TrimExcess(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="List{T}.TrueForAll(Predicate{T})"/> | ||||
|     /// </summary> | ||||
|     /// <param name="match"></param> | ||||
|     /// <returns></returns> | ||||
|     public bool TrueForAll(Predicate<T> match) | ||||
|     { | ||||
|         lock (((ICollection)this.m_list).SyncRoot) | ||||
|         { | ||||
|             return this.m_list.TrueForAll(match); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| { | ||||
|   "Demo": { | ||||
|     "IsDemo": false, | ||||
|     "UpPWEnable": false //是否允许修改密码 | ||||
|   } | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| { | ||||
|   "Demo": { | ||||
|     "IsDemo": false, | ||||
|     "UpPWEnable": false //是否允许修改密码 | ||||
|   } | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| { | ||||
|   //日志配置 | ||||
|   "LogJob": { | ||||
|     "DaysAgo": 10 //清理日志 | ||||
|   } | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| { | ||||
|   //日志配置 | ||||
|   "LogJob": { | ||||
|     "DaysAgo": 10 //清理日志 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										70
									
								
								src/ThingsGateway.Admin.Application/Const/CacheConst.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,70 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application | ||||
| { | ||||
|     public class CacheConst | ||||
|     { | ||||
|         public const string Cache_Prefix_Admin = "ThingsGatewayAdmin:"; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 系统字典表缓存Key | ||||
|         /// </summary> | ||||
|         public const string Cache_SysDict = $"{CacheConst.Cache_Prefix_Admin}SysDict:"; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 用户表缓存Key | ||||
|         /// </summary> | ||||
|         public const string Cache_SysUser = $"{CacheConst.Cache_Prefix_Admin}SysUser:"; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 用户账号关系缓存Key | ||||
|         /// </summary> | ||||
|         public const string Cache_SysUserAccount = $"{CacheConst.Cache_Prefix_Admin}SysUserAccount:"; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 资源表缓存Key | ||||
|         /// </summary> | ||||
|         public const string Cache_SysResource = $"{CacheConst.Cache_Prefix_Admin}SysResource:"; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 关系表缓存Key | ||||
|         /// </summary> | ||||
|         public const string Cache_SysRelation = $"{CacheConst.Cache_Prefix_Admin}SysRelation:"; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 角色表缓存Key | ||||
|         /// </summary> | ||||
|         public const string Cache_SysRole = $"{CacheConst.Cache_Prefix_Admin}SysRole:"; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Token表缓存Key | ||||
|         /// </summary> | ||||
|         public const string Cache_Token = $"{CacheConst.Cache_Prefix_Admin}Token:"; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Token表缓存Key | ||||
|         /// </summary> | ||||
|         public const string Cache_HardwareInfo = $"{CacheConst.Cache_Prefix_Admin}Cache_HardwareInfo:"; | ||||
|  | ||||
|         #region 登录错误次数 | ||||
|  | ||||
|         /// <summary> | ||||
|         ///  登录错误次数缓存Key | ||||
|         /// </summary> | ||||
|         public const string Cache_LoginErrorCount = $"{CacheConst.Cache_Prefix_Admin}LoginErrorCount:"; | ||||
|  | ||||
|         #endregion 登录错误次数 | ||||
|     } | ||||
| } | ||||
| @@ -1,154 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 分类常量 | ||||
| /// </summary> | ||||
| public class CateGoryConst | ||||
| { | ||||
|     #region 系统配置 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 系统基础 | ||||
|     /// </summary> | ||||
|     public const string Config_SYS_BASE = "SYS_BASE"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 业务定义 | ||||
|     /// </summary> | ||||
|     public const string Config_BIZ_DEFINE = "BIZ_DEFINE"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 登录策略 | ||||
|     /// </summary> | ||||
|     public const string LOGIN_POLICY = "LOGIN_POLICY"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 密码策略 | ||||
|     /// </summary> | ||||
|     public const string Config_PWD_POLICY = "PWD_POLICY"; | ||||
|  | ||||
|     #endregion 系统配置 | ||||
|  | ||||
|     #region 资源表 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 菜单 | ||||
|     /// </summary> | ||||
|     public const string Resource_MENU = "MENU"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 单页 | ||||
|     /// </summary> | ||||
|     public const string Resource_SPA = "SPA"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 按钮 | ||||
|     /// </summary> | ||||
|     public const string Resource_BUTTON = "BUTTON"; | ||||
|  | ||||
|     #endregion 资源表 | ||||
|  | ||||
|     #region 关系表 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 用户有哪些角色 | ||||
|     /// </summary> | ||||
|     public const string Relation_SYS_USER_HAS_ROLE = "SYS_USER_HAS_ROLE"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 角色有哪些资源 | ||||
|     /// </summary> | ||||
|     public const string Relation_SYS_ROLE_HAS_RESOURCE = "SYS_ROLE_HAS_RESOURCE"; | ||||
|  | ||||
|     /// <summary> | ||||
|     ///用户有哪些资源 | ||||
|     /// </summary> | ||||
|     public const string Relation_SYS_USER_HAS_RESOURCE = "SYS_USER_HAS_RESOURCE"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 角色有哪些权限 | ||||
|     /// </summary> | ||||
|     public const string Relation_SYS_ROLE_HAS_PERMISSION = "SYS_ROLE_HAS_PERMISSION"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 角色有哪些OPENAPI权限 | ||||
|     /// </summary> | ||||
|     public const string Relation_SYS_ROLE_HAS_OPENAPIPERMISSION = "SYS_ROLE_HAS_OPENAPIPERMISSION"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 用户有哪些权限 | ||||
|     /// </summary> | ||||
|     public const string Relation_SYS_USER_HAS_PERMISSION = "SYS_USER_HAS_PERMISSION"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 用户有哪些OPENAPI权限 | ||||
|     /// </summary> | ||||
|     public const string Relation_SYS_USER_HAS_OPENAPIPERMISSION = "Relation_SYS_USER_HAS_OPENAPIPERMISSION"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 用户工作台数据 | ||||
|     /// </summary> | ||||
|     public const string Relation_SYS_USER_WORKBENCH_DATA = "SYS_USER_WORKBENCH_DATA"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 用户主页数据 | ||||
|     /// </summary> | ||||
|     public const string Relation_SYS_USER_DEFAULT_RAZOR = "Relation_SYS_USER_DEFAULT_RAZOR"; | ||||
|  | ||||
|     #endregion 关系表 | ||||
|  | ||||
|     #region 日志表 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 登录 | ||||
|     /// </summary> | ||||
|     public const string Log_LOGIN = "LOGIN"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 登出 | ||||
|     /// </summary> | ||||
|     public const string Log_LOGOUT = "LOGOUT"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 操作 | ||||
|     /// </summary> | ||||
|     public const string Log_OPERATE = "OPERATE"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 异常 | ||||
|     /// </summary> | ||||
|     public const string Log_EXCEPTION = "EXCEPTION"; | ||||
|  | ||||
|     #endregion 日志表 | ||||
|  | ||||
|     #region 角色表 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 全局 | ||||
|     /// </summary> | ||||
|     public const string Role_GLOBAL = "GLOBAL"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Api | ||||
|     /// </summary> | ||||
|     public const string Role_API = "API"; | ||||
|  | ||||
|     #endregion 角色表 | ||||
|  | ||||
|     #region Api分组 | ||||
|  | ||||
|     public const string ThingsGatewayAdmin = "ThingsGateway.Admin"; | ||||
|     public const string ThingsGatewayApi = "ThingsGateway.OpenApi"; | ||||
|  | ||||
|     #endregion Api分组 | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,7 +9,10 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 授权用户常量 | ||||
| @@ -21,7 +25,7 @@ public class ClaimConst | ||||
|     public const string Account = "Account"; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 账号类型 | ||||
|     /// SuperAdmin | ||||
|     /// </summary> | ||||
|     public const string SuperAdmin = "SuperAdmin"; | ||||
| 
 | ||||
| @@ -1,95 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 系统配置常量 | ||||
| /// </summary> | ||||
| public class ConfigConst | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 系统默认工作台 | ||||
|     /// </summary> | ||||
|     public const string SYS_DEFAULT_WORKBENCH_DATA = "SYS_DEFAULT_WORKBENCH_DATA"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 系统默认主页 | ||||
|     /// </summary> | ||||
|     public const string SYS_DEFAULT_DEFAULT_RAZOR = "SYS_DEFAULT_DEFAULT_RAZOR"; | ||||
|  | ||||
|     #region 登录策略 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 登录验证码开关 | ||||
|     /// </summary> | ||||
|     public const string LOGIN_CAPTCHA_OPEN = "LOGIN_CAPTCHA_OPEN"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 单用户登录开关 | ||||
|     /// </summary> | ||||
|     public const string LOGIN_SINGLE_OPEN = "LOGIN_SINGLE_OPEN"; | ||||
|  | ||||
|     /// <summary> | ||||
|     ///  登录错误锁定时长 | ||||
|     /// </summary> | ||||
|     public const string LOGIN_ERROR_LOCK = "LOGIN_ERROR_LOCK"; | ||||
|  | ||||
|     /// <summary> | ||||
|     ///  登录错误锁定时长 | ||||
|     /// </summary> | ||||
|     public const string LOGIN_ERROR_RESET_TIME = "LOGIN_ERROR_RESET_TIME"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 登录错误次数 | ||||
|     /// </summary> | ||||
|     public const string LOGIN_ERROR_COUNT = "LOGIN_ERROR_COUNT"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Verificat过期时间(分) | ||||
|     /// </summary> | ||||
|     public const string LOGIN_VERIFICAT_EXPIRES = "LOGIN_VERIFICAT_EXPIRES"; | ||||
|  | ||||
|     #endregion 登录策略 | ||||
|  | ||||
|     #region 密码策略 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 默认用户密码 | ||||
|     /// </summary> | ||||
|     public const string PWD_DEFAULT_PASSWORD = "PWD_DEFAULT_PASSWORD"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 密码最小长度 | ||||
|     /// </summary> | ||||
|     public const string PWD_MIN_LENGTH = "PWD_MIN_LENGTH"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 包含数字 | ||||
|     /// </summary> | ||||
|     public const string PWD_CONTAIN_NUM = "PWD_CONTAIN_NUM"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 包含小写字母 | ||||
|     /// </summary> | ||||
|     public const string PWD_CONTAIN_LOWER = "PWD_CONTAIN_LOWER"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 包含大写字母 | ||||
|     /// </summary> | ||||
|     public const string PWD_CONTAIN_UPPER = "PWD_CONTAIN_UPPER"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 包含特殊字符 | ||||
|     /// </summary> | ||||
|     public const string PWD_CONTAIN_CHARACTER = "PWD_CONTAIN_CHARACTER"; | ||||
|  | ||||
|     #endregion 密码策略 | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,6 +9,9 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -16,7 +20,22 @@ namespace ThingsGateway.Admin.Application; | ||||
| public class ResourceConst | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 系统内置单页面编码 | ||||
|     /// 系统内置编码 | ||||
|     /// </summary> | ||||
|     public const string System = "system"; | ||||
|     public const string System = "System"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 系统管理内置ID 1 | ||||
|     /// </summary> | ||||
|     public const long SystemId = 2; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// SPA内置ID 2 | ||||
|     /// </summary> | ||||
|     public const long SpaId = 1; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// SPA内置 | ||||
|     /// </summary> | ||||
|     public const string SpaTitle = "SPA"; | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,6 +9,9 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -15,13 +19,23 @@ namespace ThingsGateway.Admin.Application; | ||||
| /// </summary> | ||||
| public class RoleConst | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 超级管理员Id | ||||
|     /// </summary> | ||||
|     public const long SuperAdminId = 212725263002001; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 超级管理员 | ||||
|     /// </summary> | ||||
|     public const string SuperAdmin = "superAdmin"; | ||||
|     public const string SuperAdmin = "SuperAdmin"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 业务管理员 | ||||
|     /// </summary> | ||||
|     public const string BizAdmin = "bizAdmin"; | ||||
| } | ||||
|     public const string BizAdmin = "BizAdmin"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// api角色 | ||||
|     /// </summary> | ||||
|     public const string ApiRole = "ApiRole"; | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,7 +9,10 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// SqlSugar系统常量 | ||||
| @@ -25,6 +29,16 @@ public class SqlSugarConst | ||||
|     /// </summary> | ||||
|     public const string DB_Log = "DB_Log"; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// DB_TokenCache | ||||
|     /// </summary> | ||||
|     public const string DB_TokenCache = "DB_TokenCache"; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// DB_HardwareInfo | ||||
|     /// </summary> | ||||
|     public const string DB_HardwareInfo = "DB_HardwareInfo"; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// DB_Custom | ||||
|     /// </summary> | ||||
| @@ -1,71 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 系统层常量 | ||||
| /// </summary> | ||||
| public class SystemConst | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 系统配置表缓存Key | ||||
|     /// </summary> | ||||
|     public const string Cache_DevConfig = CacheConst.Cache_Prefix_Admin + "SysConfig:"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 登录验证码缓存Key | ||||
|     /// </summary> | ||||
|     public const string Cache_Captcha = CacheConst.Cache_Prefix_Admin + "Captcha:"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 用户表缓存Key | ||||
|     /// </summary> | ||||
|     public const string Cache_SysUser = CacheConst.Cache_Prefix_Admin + "SysUser"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 用户手机号关系缓存Key | ||||
|     /// </summary> | ||||
|     public const string Cache_SysUserPhone = CacheConst.Cache_Prefix_Admin + "SysUserPhone"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 用户手机号关系缓存Key | ||||
|     /// </summary> | ||||
|     public const string Cache_SysUserAccount = CacheConst.Cache_Prefix_Admin + "SysUserAccount"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 资源表缓存Key | ||||
|     /// </summary> | ||||
|     public const string Cache_SysResource = CacheConst.Cache_Prefix_Admin + "SysResource:"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 字典表缓存Key | ||||
|     /// </summary> | ||||
|     public const string Cache_DevDict = CacheConst.Cache_Prefix_Admin + "DevDict"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 关系表缓存Key | ||||
|     /// </summary> | ||||
|     public const string Cache_SysRelation = CacheConst.Cache_Prefix_Admin + "SysRelation:"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 角色表缓存Key | ||||
|     /// </summary> | ||||
|     public const string Cache_SysRole = CacheConst.Cache_Prefix_Admin + "SysRole"; | ||||
|  | ||||
|     #region 登录错误次数 | ||||
|  | ||||
|     /// <summary> | ||||
|     ///  登录错误次数缓存Key | ||||
|     /// </summary> | ||||
|     public const string Cache_LoginErrorCount = CacheConst.Cache_Prefix_Admin + "LoginErrorCount:"; | ||||
|  | ||||
|     #endregion 登录错误次数 | ||||
| } | ||||
| @@ -0,0 +1,51 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using Microsoft.AspNetCore.Authentication.Cookies; | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.AspNetCore.WebUtilities; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| [Route("api/auth")] | ||||
| [LoggingMonitor] | ||||
| public class AuthController : ControllerBase | ||||
| { | ||||
|     private readonly IAuthService _authService; | ||||
|  | ||||
|     public AuthController(IAuthService authService) | ||||
|     { | ||||
|         _authService = authService; | ||||
|     } | ||||
|  | ||||
|     [HttpPost("login")] | ||||
|     [AllowAnonymous] | ||||
|     public   Task<LoginOutput> LoginAsync([FromBody] LoginInput input) | ||||
|     { | ||||
|         return   _authService.LoginAsync(input); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("logout")] | ||||
|     [Authorize] | ||||
|     [IgnoreRolePermission] | ||||
|     public async Task<IActionResult> LogoutAsync([FromQuery] string returnUrl) | ||||
|     { | ||||
|         await _authService.LoginOutAsync(); | ||||
|         return Redirect(QueryHelpers.AddQueryString(Request.PathBase + CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?> | ||||
|         { | ||||
|             ["ReturnUrl"] = returnUrl | ||||
|         })); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,75 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.AspNetCore.Localization; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
|  | ||||
| using RouteAttribute = Microsoft.AspNetCore.Mvc.RouteAttribute; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 文化 Controller | ||||
| /// </summary> | ||||
| [Route("[controller]/[action]")] | ||||
| public class CultureController : Controller | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 设置文化方法 | ||||
|     /// </summary> | ||||
|     /// <param name="culture"></param> | ||||
|     /// <param name="redirectUri"></param> | ||||
|     /// <returns></returns> | ||||
|     [HttpGet] | ||||
|     public IActionResult SetCulture(string culture, string redirectUri) | ||||
|     { | ||||
|         if (string.IsNullOrEmpty(culture)) | ||||
|         { | ||||
|             HttpContext.Response.Cookies.Delete(CookieRequestCultureProvider.DefaultCookieName); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             HttpContext.Response.Cookies.Append( | ||||
|                 CookieRequestCultureProvider.DefaultCookieName, | ||||
|                 CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture, culture)), new CookieOptions() | ||||
|                 { | ||||
|                     Expires = DateTimeOffset.Now.AddYears(1) | ||||
|                 }); | ||||
|  | ||||
|             //更改全局文化,采集后台也会变化 | ||||
|             //var cultureInfo = new CultureInfo(culture); | ||||
|             //CultureInfo.DefaultThreadCurrentCulture = cultureInfo; | ||||
|             //CultureInfo.DefaultThreadCurrentUICulture = cultureInfo; | ||||
|  | ||||
|             //CultureInfo.CurrentCulture = cultureInfo; | ||||
|             //CultureInfo.CurrentUICulture = cultureInfo; | ||||
|         } | ||||
|  | ||||
|         return LocalRedirect(redirectUri); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 重置文化方法 | ||||
|     /// </summary> | ||||
|     /// <param name="redirectUri"></param> | ||||
|     /// <returns></returns> | ||||
|     [HttpGet] | ||||
|     public IActionResult ResetCulture(string redirectUri) | ||||
|     { | ||||
|         HttpContext.Response.Cookies.Delete(CookieRequestCultureProvider.DefaultCookieName); | ||||
|  | ||||
|         return LocalRedirect(redirectUri); | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,22 +9,18 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| 
 | ||||
| namespace ThingsGateway.Demo.Web | ||||
| namespace ThingsGateway.Admin.Application | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 文件下载 | ||||
|     /// </summary> | ||||
| #if DEMO | ||||
| #else | ||||
| 
 | ||||
|     [ApiDescriptionSettings(IgnoreApi = true)] | ||||
| #endif | ||||
| 
 | ||||
|     [ApiController] | ||||
|     [Route("[controller]")]
 | ||||
|     [Route("api/file")] | ||||
|     public class FileController : ControllerBase | ||||
|     { | ||||
|         /// <summary> | ||||
| @@ -31,7 +28,7 @@ namespace ThingsGateway.Demo.Web | ||||
|         /// </summary> | ||||
|         /// <param name="fileName">相对路径</param> | ||||
|         /// <returns></returns> | ||||
|         [HttpGet] | ||||
|         [HttpGet("download")] | ||||
|         public IActionResult Download(string fileName) | ||||
|         { | ||||
|             var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", fileName); | ||||
| @@ -0,0 +1,350 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.AspNetCore.Mvc.Controllers; | ||||
| using Microsoft.AspNetCore.Mvc.Filters; | ||||
| using Microsoft.AspNetCore.Mvc.Infrastructure; | ||||
| using Microsoft.AspNetCore.Mvc.ModelBinding; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
|  | ||||
| using System.Collections; | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.Reflection; | ||||
| using System.Text.Encodings.Web; | ||||
| using System.Text.Json; | ||||
|  | ||||
| using ThingsGateway.Core.Json.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 规范化RESTful风格返回值 | ||||
| /// </summary> | ||||
| public class ResultFilter : IAsyncActionFilter | ||||
| { | ||||
|     public const string ValidationFailedKey = $"{nameof(ResultFilter)}Validate"; | ||||
|  | ||||
|     public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) | ||||
|     { | ||||
|         // 排除 WebSocket 请求处理 | ||||
|         if (context.HttpContext.IsWebSocketRequest()) | ||||
|         { | ||||
|             await next(); | ||||
|             return; | ||||
|         } | ||||
|         var httpContext = context.HttpContext; | ||||
|         var unifyResult = httpContext.RequestServices.GetRequiredService<IUnifyResultProvider>(); | ||||
|  | ||||
|         #region 验证 | ||||
|  | ||||
|         // 解析验证消息 | ||||
|         { | ||||
|             if (!context.ModelState.IsValid) | ||||
|             { | ||||
|                 var allValidationResults = new List<ValidationResult>(); | ||||
|                 int errorCount = 0; | ||||
|                 //重新获取错误信息 | ||||
|                 foreach (var item in context.ActionArguments) | ||||
|                 { | ||||
|                     if (errorCount == context.ModelState.ErrorCount) break; | ||||
|                     foreach (var parameter in context.ModelState) | ||||
|                     { | ||||
|                         if (errorCount == context.ModelState.ErrorCount) break; | ||||
|                         var validationResults = new List<ValidationResult>(); | ||||
|                         var validationContext = new ValidationContext(item.Value!); | ||||
|                         ValidateProperty(validationContext, validationResults, parameter.Key); | ||||
|                         allValidationResults.AddRange(validationResults); | ||||
|                         errorCount += validationResults.Count; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 //var validationMetadata = GetValidationMetadata(context.ModelState!); | ||||
|                 if (allValidationResults.Count > 0) | ||||
|                 { | ||||
|                     string? errorMessage; | ||||
|                     if (allValidationResults.Count == 1) | ||||
|                     { | ||||
|                         errorMessage = allValidationResults.FirstOrDefault()!.ErrorMessage; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         errorMessage = allValidationResults!.ToDictionary(a => a.MemberNames.FirstOrDefault()!, a => a.ErrorMessage).ToSystemTextJsonString(); | ||||
|                     } | ||||
|                     var result = unifyResult.OnValidateFailed(context, errorMessage); | ||||
|                     if (result != null) | ||||
|                     { | ||||
|                         context.Result = result; | ||||
|  | ||||
|                         // 存储验证执行结果 | ||||
|                         context.HttpContext.Items[ValidationFailedKey] = errorMessage; | ||||
|  | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         #endregion 验证 | ||||
|  | ||||
|         // 执行 Action 并获取结果 | ||||
|         ActionExecutedContext? actionExecutedContext = await next(); | ||||
|  | ||||
|         #region 异常 | ||||
|  | ||||
|         // 如果出现异常 | ||||
|         if (actionExecutedContext.Exception != null) | ||||
|         { | ||||
|             // 判断是否支持 MVC 规范化处理 | ||||
|             if (UnifyContext.CheckHttpContextNonUnify(httpContext)) return; | ||||
|             // 执行规范化异常处理 | ||||
|             actionExecutedContext.Result = unifyResult.OnException(actionExecutedContext); | ||||
|             actionExecutedContext.ExceptionHandled = true; | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         #endregion 异常 | ||||
|  | ||||
|         #region 状态码 | ||||
|  | ||||
|         // 处理已经含有状态码结果的 Result | ||||
|         if (actionExecutedContext.Result is IStatusCodeActionResult statusCodeResult && statusCodeResult.StatusCode != null) | ||||
|         { | ||||
|             // 小于 200 或者 大于 299 都不是成功值,直接跳过 | ||||
|             if (statusCodeResult.StatusCode.Value < 200 || statusCodeResult.StatusCode.Value > 299) | ||||
|             { | ||||
|                 // 处理规范化结果 | ||||
|                 if (!UnifyContext.CheckStatusCodeNonUnify(httpContext)) | ||||
|                 { | ||||
|                     var statusCode = statusCodeResult.StatusCode.Value; | ||||
|  | ||||
|                     // 解决刷新 Token 时间和 Token 时间相近问题 | ||||
|                     if (statusCodeResult.StatusCode.Value == StatusCodes.Status401Unauthorized | ||||
|                         && httpContext.Response.Headers.ContainsKey("access-token") | ||||
|                         && httpContext.Response.Headers.ContainsKey("x-access-token")) | ||||
|                     { | ||||
|                         httpContext.Response.StatusCode = statusCode = StatusCodes.Status403Forbidden; | ||||
|                     } | ||||
|  | ||||
|                     // 如果 Response 已经完成输出,则禁止写入 | ||||
|                     if (httpContext.Response.HasStarted) return; | ||||
|  | ||||
|                     await unifyResult.OnResponseStatusCodes(httpContext, statusCode); | ||||
|                 } | ||||
|  | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         #endregion 状态码 | ||||
|  | ||||
|         #region 成功 | ||||
|  | ||||
|         // 获取控制器信息 | ||||
|         var actionDescriptor = actionExecutedContext.ActionDescriptor as ControllerActionDescriptor; | ||||
|  | ||||
|         // 判断是否支持 MVC 规范化处理或特定检查 | ||||
|         if (UnifyContext.CheckHttpContextNonUnify(httpContext)) return; | ||||
|  | ||||
|         // 判断是否跳过规范化处理 | ||||
|         if (UnifyContext.CheckSucceededNonUnify(actionDescriptor!.MethodInfo)) return; | ||||
|  | ||||
|         // 处理 BadRequestObjectResult 类型规范化处理 | ||||
|         if (actionExecutedContext.Result is BadRequestObjectResult badRequestObjectResult) | ||||
|         { | ||||
|             // 解析验证消息 | ||||
|             var validationMetadata = GetValidationMetadata(badRequestObjectResult.Value!); | ||||
|  | ||||
|             var result = unifyResult.OnValidateFailed(context, validationMetadata); | ||||
|             if (result != null) actionExecutedContext.Result = result; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             IActionResult? result = default; | ||||
|  | ||||
|             // 检查是否是有效的结果(可进行规范化的结果) | ||||
|             if (UnifyContext.CheckVaildResult(actionExecutedContext.Result!, out var data)) | ||||
|             { | ||||
|                 result = unifyResult.OnSucceeded(actionExecutedContext, data); | ||||
|             } | ||||
|  | ||||
|             // 如果是不能规范化的结果类型,则跳过 | ||||
|             if (result == null) return; | ||||
|  | ||||
|             actionExecutedContext.Result = result; | ||||
|         } | ||||
|  | ||||
|         #endregion 成功 | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取验证错误信息 | ||||
|     /// </summary> | ||||
|     /// <param name="errors"></param> | ||||
|     /// <returns></returns> | ||||
|     internal static string GetValidationMetadata(object errors) | ||||
|     { | ||||
|         object? validationResults = null; | ||||
|         string? message = default; | ||||
|  | ||||
|         // 判断是否是集合类型 | ||||
|         if (errors is IEnumerable && errors is not string) | ||||
|         { | ||||
|             // 如果是模型验证字典类型 | ||||
|             if (errors is ModelStateDictionary modelState) | ||||
|             { | ||||
|                 // 将验证错误信息转换成字典并序列化成 Json | ||||
|                 validationResults = modelState.Where(u => modelState[u.Key]!.ValidationState == ModelValidationState.Invalid) | ||||
|                         .ToDictionary(u => u.Key, u => modelState[u.Key]!.Errors.Select(c => c.ErrorMessage).ToArray()); | ||||
|             } | ||||
|             // 如果是 ValidationProblemDetails 特殊类型 | ||||
|             else if (errors is ValidationProblemDetails validation) | ||||
|             { | ||||
|                 validationResults = validation.Errors | ||||
|                     .ToDictionary(u => u.Key, u => u.Value.ToArray()); | ||||
|             } | ||||
|             // 如果是字典类型 | ||||
|             else if (errors is IDictionary<string, string[]> dicResults) | ||||
|             { | ||||
|                 validationResults = dicResults; | ||||
|             } | ||||
|  | ||||
|             message = JsonSerializer.Serialize(validationResults, new JsonSerializerOptions | ||||
|             { | ||||
|                 Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, | ||||
|                 WriteIndented = true | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取异常元数据 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string GetExceptionMetadata(ActionContext context) | ||||
|     { | ||||
|         // 判断是否是 ExceptionContext 或者 ActionExecutedContext | ||||
|         var exception = context is ExceptionContext exContext | ||||
|             ? exContext.Exception | ||||
|             : ( | ||||
|                 context is ActionExecutedContext edContext | ||||
|                 ? edContext.Exception | ||||
|                 : default | ||||
|             ); | ||||
|  | ||||
|         string? errors = exception?.InnerException?.Message ?? exception?.Message; | ||||
|         return errors; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 验证整个模型时验证属性方法 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <param name="results"></param> | ||||
|     /// <param name="pName"></param> | ||||
|     private void ValidateProperty(ValidationContext context, List<ValidationResult> results, string pName) | ||||
|     { | ||||
|         // 获得所有可写属性 | ||||
|         var pi = context.ObjectType.GetPropertyByName(pName); | ||||
|         if (pi != null) | ||||
|         { | ||||
|             // 设置其关联属性字段 | ||||
|             var propertyValue = pi.GetValue(context.ObjectInstance); | ||||
|             var fieldIdentifier = new FieldIdentifier(context.ObjectInstance, pi.Name); | ||||
|             context.DisplayName = fieldIdentifier.GetDisplayName(); | ||||
|             context.MemberName = fieldIdentifier.FieldName; | ||||
|  | ||||
|             // 组件进行验证 | ||||
|             ValidateDataAnnotations(propertyValue, context, results, pi); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 通过属性设置的 DataAnnotation 进行数据验证 | ||||
|     /// </summary> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="context"></param> | ||||
|     /// <param name="results"></param> | ||||
|     /// <param name="propertyInfo"></param> | ||||
|     /// <param name="memberName"></param> | ||||
|     private void ValidateDataAnnotations(object? value, ValidationContext context, List<ValidationResult> results, PropertyInfo propertyInfo, string? memberName = null) | ||||
|     { | ||||
|         var rules = propertyInfo.GetCustomAttributes(true).OfType<ValidationAttribute>().ToList(); | ||||
|         var metadataType = context.ObjectType.GetCustomAttribute<MetadataTypeAttribute>(false); | ||||
|         if (metadataType != null) | ||||
|         { | ||||
|             var p = metadataType.MetadataClassType.GetPropertyByName(propertyInfo.Name); | ||||
|             if (p != null) | ||||
|             { | ||||
|                 rules.AddRange(p.GetCustomAttributes(true).OfType<ValidationAttribute>()); | ||||
|             } | ||||
|         } | ||||
|         var displayName = context.DisplayName; | ||||
|         memberName ??= propertyInfo.Name; | ||||
|         var attributeSpan = nameof(Attribute).AsSpan(); | ||||
|         foreach (var rule in rules) | ||||
|         { | ||||
|             var result = rule.GetValidationResult(value, context); | ||||
|             if (result != null && result != ValidationResult.Success) | ||||
|             { | ||||
|                 // 查找 resx 资源文件中的 ErrorMessage | ||||
|                 var ruleNameSpan = rule.GetType().Name.AsSpan(); | ||||
|                 var index = ruleNameSpan.IndexOf(attributeSpan, StringComparison.OrdinalIgnoreCase); | ||||
|                 var ruleName = ruleNameSpan[..index]; | ||||
|                 var find = false; | ||||
|  | ||||
|                 // 通过设置 ErrorMessage 检索 | ||||
|                 if (!context.ObjectType.Assembly.IsDynamic && !find | ||||
|                     && !string.IsNullOrEmpty(rule.ErrorMessage) | ||||
|                     && App.CreateLocalizerByType(context.ObjectType)!.TryGetLocalizerString(rule.ErrorMessage, out var msg)) | ||||
|                 { | ||||
|                     rule.ErrorMessage = msg; | ||||
|                     find = true; | ||||
|                 } | ||||
|  | ||||
|                 // 通过 Attribute 检索 | ||||
|                 if (!rule.GetType().Assembly.IsDynamic && !find | ||||
|                     && App.CreateLocalizerByType(rule.GetType())!.TryGetLocalizerString(nameof(rule.ErrorMessage), out msg)) | ||||
|                 { | ||||
|                     rule.ErrorMessage = msg; | ||||
|                     find = true; | ||||
|                 } | ||||
|                 // 通过 字段.规则名称 检索 | ||||
|                 if (!context.ObjectType.Assembly.IsDynamic && !find | ||||
|                     && App.CreateLocalizerByType(context.ObjectType)!.TryGetLocalizerString($"{memberName}.{ruleName.ToString()}", out msg)) | ||||
|                 { | ||||
|                     rule.ErrorMessage = msg; | ||||
|                     find = true; | ||||
|                 } | ||||
|  | ||||
|                 if (!find) | ||||
|                 { | ||||
|                     rule.ErrorMessage = result.ErrorMessage; | ||||
|                 } | ||||
|                 var errorMessage = !string.IsNullOrEmpty(rule.ErrorMessage) && rule.ErrorMessage.Contains("{0}") | ||||
|                     ? rule.FormatErrorMessage(displayName) | ||||
|                     : rule.ErrorMessage; | ||||
|                 results.Add(new ValidationResult(errorMessage, new string[] { memberName })); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,58 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.AspNetCore.Mvc.Filters; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 规范化结果提供器 | ||||
| /// </summary> | ||||
| public interface IUnifyResultProvider | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 异常返回值 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <returns></returns> | ||||
|     IActionResult OnException(ActionExecutedContext context); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 成功返回值 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <param name="data"></param> | ||||
|     /// <returns></returns> | ||||
|     IActionResult OnSucceeded(ActionExecutedContext context, object? data); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 验证失败返回值 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <param name="errors"></param> | ||||
|     /// <returns></returns> | ||||
|     IActionResult OnValidateFailed(ActionExecutingContext context, string? errors); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 拦截返回状态码 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <param name="statusCode"></param> | ||||
|     /// <returns></returns> | ||||
|     Task OnResponseStatusCodes(HttpContext context, int statusCode); | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,9 +9,17 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| global using Furion.DynamicApiController; | ||||
| 
 | ||||
| global using System; | ||||
| global using System.Threading.Tasks; | ||||
| 
 | ||||
| global using ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
| 
 | ||||
| namespace Microsoft.AspNetCore.Mvc; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 禁止规范化处理 | ||||
| /// </summary> | ||||
| [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] | ||||
| public sealed class NonUnifyAttribute : Attribute | ||||
| { | ||||
| } | ||||
| @@ -0,0 +1,149 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.AspNetCore.Http.Features; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.AspNetCore.Mvc.ApiExplorer; | ||||
| using Microsoft.AspNetCore.Mvc.RazorPages; | ||||
|  | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 规范化结果上下文 | ||||
| /// </summary> | ||||
| public static class UnifyContext | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 是否启用规范化结果 | ||||
|     /// </summary> | ||||
|     internal static bool EnabledUnifyHandler = true; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 检查请求成功是否进行规范化处理 | ||||
|     /// </summary> | ||||
|     /// <param name="method"></param> | ||||
|     /// <returns>返回 true 跳过处理,否则进行规范化处理</returns> | ||||
|     internal static bool CheckSucceededNonUnify(MethodInfo method) | ||||
|     { | ||||
|         // 判断是否跳过规范化处理 | ||||
|         var isSkip = !EnabledUnifyHandler | ||||
|               || method.CustomAttributes.Any(x => typeof(NonUnifyAttribute).IsAssignableFrom(x.AttributeType) || typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType)) | ||||
|               || method.ReflectedType!.IsDefined(typeof(NonUnifyAttribute), true) | ||||
|               || method.DeclaringType!.Assembly.GetName().Name!.StartsWith("Microsoft.AspNetCore.OData"); | ||||
|  | ||||
|         return isSkip; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 检查短路状态码(>=400)是否进行规范化处理 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <returns>返回 true 跳过处理,否则进行规范化处理</returns> | ||||
|     internal static bool CheckStatusCodeNonUnify(HttpContext context) | ||||
|     { | ||||
|         // 获取终点路由特性 | ||||
|         var endpointFeature = context.Features.Get<IEndpointFeature>(); | ||||
|         if (endpointFeature == null) return false; | ||||
|  | ||||
|         // 判断是否跳过规范化处理 | ||||
|         var isSkip = !EnabledUnifyHandler | ||||
|               || context.Request.Headers["accept"].ToString().Contains("odata.metadata=", StringComparison.OrdinalIgnoreCase) | ||||
|                 || context.Request.Headers["accept"].ToString().Contains("odata.streaming=", StringComparison.OrdinalIgnoreCase) | ||||
|                 || ResponseContentTypesOfNonUnify.Any(u => context.Response.Headers["content-type"].ToString().Contains(u, StringComparison.OrdinalIgnoreCase) | ||||
|                 || context.GetMetadata<NonUnifyAttribute>() != null | ||||
|                 || endpointFeature?.Endpoint?.Metadata?.GetMetadata<NonUnifyAttribute>() != null | ||||
|               ); | ||||
|  | ||||
|         return isSkip; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 跳过规范化处理的 Response Content-Type | ||||
|     /// </summary> | ||||
|     internal static string[] ResponseContentTypesOfNonUnify = new[] | ||||
|     { | ||||
|         "text/event-stream", | ||||
|         "application/pdf", | ||||
|         "application/octet-stream", | ||||
|         "image/" | ||||
|     }; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 检查 HttpContext 是否进行规范化处理 | ||||
|     /// </summary> | ||||
|     /// <param name="httpContext"></param> | ||||
|     /// <returns>返回 true 跳过处理,否则进行规范化处理</returns> | ||||
|     internal static bool CheckHttpContextNonUnify(HttpContext httpContext) | ||||
|     { | ||||
|         var contentType = httpContext.Response.Headers["content-type"].ToString(); | ||||
|         if (ResponseContentTypesOfNonUnify.Any(u => contentType.Contains(u, StringComparison.OrdinalIgnoreCase))) | ||||
|         { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 检查是否是有效的结果(可进行规范化的结果) | ||||
|     /// </summary> | ||||
|     /// <param name="result"></param> | ||||
|     /// <param name="data"></param> | ||||
|     /// <returns></returns> | ||||
|     internal static bool CheckVaildResult(IActionResult result, out object? data) | ||||
|     { | ||||
|         data = default; | ||||
|  | ||||
|         // 排除以下结果,跳过规范化处理 | ||||
|         var isDataResult = result switch | ||||
|         { | ||||
|             ViewResult => false, | ||||
|             PartialViewResult => false, | ||||
|             FileResult => false, | ||||
|             ChallengeResult => false, | ||||
|             SignInResult => false, | ||||
|             SignOutResult => false, | ||||
|             RedirectToPageResult => false, | ||||
|             RedirectToRouteResult => false, | ||||
|             RedirectResult => false, | ||||
|             RedirectToActionResult => false, | ||||
|             LocalRedirectResult => false, | ||||
|             ForbidResult => false, | ||||
|             ViewComponentResult => false, | ||||
|             PageResult => false, | ||||
|             NotFoundResult => false, | ||||
|             NotFoundObjectResult => false, | ||||
|             _ => true, | ||||
|         }; | ||||
|  | ||||
|         // 目前支持返回值 ActionResult | ||||
|         if (isDataResult) data = result switch | ||||
|         { | ||||
|             // 处理内容结果 | ||||
|             ContentResult content => content.Content, | ||||
|             // 处理对象结果 | ||||
|             ObjectResult obj => obj.Value, | ||||
|             // 处理 JSON 对象 | ||||
|             JsonResult json => json.Value, | ||||
|             _ => null, | ||||
|         }; | ||||
|  | ||||
|         return isDataResult; | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,7 +9,10 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 全局返回结果 | ||||
| @@ -24,17 +28,12 @@ public class UnifyResult<T> | ||||
|     /// <summary> | ||||
|     /// 数据 | ||||
|     /// </summary> | ||||
|     public T Data { get; set; } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 附加数据 | ||||
|     /// </summary> | ||||
|     public object Extras { get; set; } | ||||
|     public T? Data { get; set; } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 错误信息 | ||||
|     /// </summary> | ||||
|     public object Msg { get; set; } | ||||
|     public object? Msg { get; set; } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 时间 | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,31 +9,32 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| using Furion.DataValidation; | ||||
| using Furion.FriendlyException; | ||||
| using Furion.UnifyResult; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.AspNetCore.Mvc.Filters; | ||||
| using Microsoft.AspNetCore.Mvc.Infrastructure; | ||||
| using Microsoft.Extensions.Localization; | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 规范化RESTful风格返回值 | ||||
| /// </summary> | ||||
| [SuppressSniffer, UnifyModel(typeof(UnifyResult<>))] | ||||
| public class UnifyResultProvider : IUnifyResultProvider | ||||
| { | ||||
|     private static IStringLocalizer Localizer = App.CreateLocalizerByType(typeof(UnifyResultProvider))!; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 异常返回 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <param name="metadata"></param> | ||||
|     /// <returns></returns> | ||||
|     public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata) | ||||
|     public IActionResult OnException(ActionExecutedContext context) | ||||
|     { | ||||
|         return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors)); | ||||
|         return new JsonResult(RESTfulResult(context.Result is IStatusCodeActionResult statusCodeResult ? statusCodeResult.StatusCode ?? 500 : 500, false, null, context.Exception?.GetTrue()?.Message)); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
| @@ -40,24 +42,18 @@ public class UnifyResultProvider : IUnifyResultProvider | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <param name="statusCode"></param> | ||||
|     /// <param name="unifyResultSettings"></param> | ||||
|     /// <returns></returns> | ||||
|     public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings = null) | ||||
|     public async Task OnResponseStatusCodes(HttpContext context, int statusCode) | ||||
|     { | ||||
|         // 设置响应状态码 | ||||
|         UnifyContext.SetResponseStatusCodes(context, statusCode, unifyResultSettings); | ||||
| 
 | ||||
|         switch (statusCode) | ||||
|         { | ||||
|             // 处理 401 状态码 | ||||
|             case StatusCodes.Status401Unauthorized: | ||||
|                 await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "登录已过期,请重新登录"), | ||||
|                     App.GetOptions<JsonOptions>()?.JsonSerializerOptions); | ||||
|                 await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, false, Localizer["TokenOver"])); | ||||
|                 break; | ||||
|             // 处理 403 状态码 | ||||
|             case StatusCodes.Status403Forbidden: | ||||
|                 await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "禁止访问,没有权限"), | ||||
|                     App.GetOptions<JsonOptions>()?.JsonSerializerOptions); | ||||
|                 await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, false, default, "NoPermission")); | ||||
|                 break; | ||||
| 
 | ||||
|             default: break; | ||||
| @@ -70,7 +66,7 @@ public class UnifyResultProvider : IUnifyResultProvider | ||||
|     /// <param name="context"></param> | ||||
|     /// <param name="data"></param> | ||||
|     /// <returns></returns> | ||||
|     public IActionResult OnSucceeded(ActionExecutedContext context, object data) | ||||
|     public IActionResult OnSucceeded(ActionExecutedContext context, object? data) | ||||
|     { | ||||
|         return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data)); | ||||
|     } | ||||
| @@ -79,11 +75,11 @@ public class UnifyResultProvider : IUnifyResultProvider | ||||
|     /// 验证失败返回 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <param name="metadata"></param> | ||||
|     /// <param name="errors"></param> | ||||
|     /// <returns></returns> | ||||
|     public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata) | ||||
|     public IActionResult OnValidateFailed(ActionExecutingContext context, string? errors) | ||||
|     { | ||||
|         return new JsonResult(RESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, errors: metadata.FirstErrorMessage ?? metadata.Message)); | ||||
|         return new JsonResult(RESTfulResult(StatusCodes.Status400BadRequest, false, null, errors)); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
| @@ -94,14 +90,13 @@ public class UnifyResultProvider : IUnifyResultProvider | ||||
|     /// <param name="data">数据</param> | ||||
|     /// <param name="errors">错误信息</param> | ||||
|     /// <returns></returns> | ||||
|     private static UnifyResult<object> RESTfulResult(int statusCode, bool succeeded = default, object data = default, object errors = default) | ||||
|     private static UnifyResult<object> RESTfulResult(int statusCode, bool succeeded = default, object? data = default, object? errors = default) | ||||
|     { | ||||
|         return new UnifyResult<object> | ||||
|         { | ||||
|             Code = statusCode, | ||||
|             Msg = statusCode == StatusCodes.Status200OK ? "请求成功" : errors, | ||||
|             Msg = statusCode == StatusCodes.Status200OK ? "Success" : errors, | ||||
|             Data = data, | ||||
|             Extras = UnifyContext.Take(), | ||||
|             Time = DateTime.Now, | ||||
|         }; | ||||
|     } | ||||
| @@ -0,0 +1,86 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.Extensions.Configuration; | ||||
|  | ||||
| using ThingsGateway.Core; | ||||
|  | ||||
| using RouteAttribute = Microsoft.AspNetCore.Mvc.RouteAttribute; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// Gitee WebHook | ||||
| /// </summary> | ||||
| [Route("api/[controller]/[action]")] | ||||
| [ApiController] | ||||
| public class GiteeController : ControllerBase | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Gitee Webhook | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     [HttpPost] | ||||
|     public IActionResult Webhook([FromQuery] string? id, [FromServices] IConfiguration config, [FromServices] IDispatchService<GiteePostBody> dispatch, [FromBody] GiteePostBody payload) | ||||
|     { | ||||
|         bool ret = false; | ||||
|         if (Check()) | ||||
|         { | ||||
|             // 全局推送 | ||||
|             if (payload.HeadCommit != null || payload.Commits?.Count > 0) | ||||
|             { | ||||
|                 dispatch.Dispatch(new DispatchEntry<GiteePostBody>() | ||||
|                 { | ||||
|                     Name = "Gitee", | ||||
|                     Entry = payload | ||||
|                 }); | ||||
|             } | ||||
|             ret = true; | ||||
|         } | ||||
|         return ret ? Ok() : Unauthorized(); | ||||
|  | ||||
|         bool Check() | ||||
|         { | ||||
|             var configId = config.GetValue<string>("WebHooks:Gitee:Id"); | ||||
|             var configToken = config.GetValue<string>("WebHooks:Gitee:Token"); | ||||
|             var token = ""; | ||||
|             if (Request.Headers.TryGetValue("X-Gitee-Token", out var val)) | ||||
|             { | ||||
|                 token = val.FirstOrDefault() ?? string.Empty; | ||||
|             } | ||||
|             return id == configId && token == configToken | ||||
|                     && payload.Id == configId && payload.Password == configToken; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Webhook 测试接口 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     [HttpGet] | ||||
|     public IActionResult Webhook() | ||||
|     { | ||||
|         return Ok(new { Message = "Ok" }); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 跨域握手协议 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     [HttpOptions] | ||||
|     public string Options() => string.Empty; | ||||
| } | ||||
| @@ -0,0 +1,51 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using Microsoft.AspNetCore.Authentication.Cookies; | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.AspNetCore.WebUtilities; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| [Route("openapi/auth")] | ||||
| [LoggingMonitor] | ||||
| public class OpenApiController : ControllerBase | ||||
| { | ||||
|     private readonly IAuthService _authService; | ||||
|  | ||||
|     public OpenApiController(IAuthService authService) | ||||
|     { | ||||
|         _authService = authService; | ||||
|     } | ||||
|  | ||||
|     [HttpPost("login")] | ||||
|     [AllowAnonymous] | ||||
|     public Task<LoginOutput> LoginAsync([FromBody] LoginInput input) | ||||
|     { | ||||
|         return _authService.LoginAsync(input, false); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("logout")] | ||||
|     [Authorize] | ||||
|     [IgnoreRolePermission] | ||||
|     public async Task<IActionResult> LogoutAsync([FromQuery] string returnUrl) | ||||
|     { | ||||
|         await _authService.LoginOutAsync(); | ||||
|         return Redirect(QueryHelpers.AddQueryString(Request.PathBase + CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?> | ||||
|         { | ||||
|             ["ReturnUrl"] = returnUrl | ||||
|         })); | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,19 +9,24 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| using Microsoft.AspNetCore.SignalR; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| using BootstrapBlazor.Components; | ||||
| 
 | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// SignalR组件 | ||||
| /// </summary> | ||||
| public sealed class SignalRComponent : IServiceComponent | ||||
| [Route("api/[controller]/[action]")]
 | ||||
| [RolePermission] | ||||
| [Authorize(AuthenticationSchemes = "Bearer")] | ||||
| public class TestController : ControllerBase | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     public void Load(IServiceCollection services, ComponentContext componentContext) | ||||
|     [HttpGet] | ||||
|     public async Task Test(QueryPageOptions queryPageOptions) | ||||
|     { | ||||
|         services.AddSingleton<IUserIdProvider, UserIdProvider>();//用户ID提供器 | ||||
|         services.AddSignalR();//注册SignalR | ||||
|         await Task.CompletedTask; | ||||
|     } | ||||
| } | ||||
| @@ -1,72 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.ComponentModel; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 会话信息 | ||||
| /// </summary> | ||||
| public class VerificatInfo : PrimaryIdEntity | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 客户端ID列表 | ||||
|     /// </summary> | ||||
|     [Newtonsoft.Json.JsonIgnore] | ||||
|     public List<string> ClientIds { get; set; } = new List<string>(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设备 | ||||
|     /// </summary> | ||||
|     [Description("设备")] | ||||
|     [DataTable(Order = 1, IsShow = true, Sortable = true)] | ||||
|     public string Device { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 过期时间 | ||||
|     /// </summary> | ||||
|     [Description("过期时间")] | ||||
|     public int Expire { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 验证Id | ||||
|     /// </summary> | ||||
|     [Description("验证Id")] | ||||
|     [DataTable(Order = 3, IsShow = true, Sortable = true)] | ||||
|     public override long Id { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 在线状态 | ||||
|     /// </summary> | ||||
|     [Description("在线状态")] | ||||
|     [DataTable(Order = 2, IsShow = true, Sortable = true)] | ||||
|     public bool IsOnline => ClientIds.Count > 0; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 连接数量 | ||||
|     /// </summary> | ||||
|     [Description("连接数量")] | ||||
|     [DataTable(Order = 4, IsShow = true, Sortable = true)] | ||||
|     public int OnlineNum => ClientIds.Count; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// verificat剩余有效期 | ||||
|     /// </summary> | ||||
|     [Description("有效期")] | ||||
|     [DataTable(Order = 5, IsShow = true, Sortable = true)] | ||||
|     public string VerificatRemain { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 超时时间 | ||||
|     /// </summary> | ||||
|     [Description("超时时间")] | ||||
|     public DateTime VerificatTimeout { get; set; } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,7 +9,18 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| using BootstrapBlazor.Components; | ||||
| 
 | ||||
| using SqlSugar; | ||||
| 
 | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| 
 | ||||
| using ThingsGateway.Core; | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 主键id基类 | ||||
| @@ -20,6 +32,7 @@ public abstract class PrimaryIdEntity : IPrimaryIdEntity | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] | ||||
|     [IgnoreExcel] | ||||
|     [AutoGenerateColumn(Visible = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)] | ||||
|     public virtual long Id { get; set; } | ||||
| } | ||||
| 
 | ||||
| @@ -33,6 +46,7 @@ public abstract class PrimaryKeyEntity : PrimaryIdEntity | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnDescription = "扩展信息", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)] | ||||
|     [IgnoreExcel] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public virtual string? ExtJson { get; set; } | ||||
| } | ||||
| 
 | ||||
| @@ -46,6 +60,7 @@ public abstract class BaseEntity : PrimaryKeyEntity | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnDescription = "创建时间", IsOnlyIgnoreUpdate = true, IsNullable = true)] | ||||
|     [IgnoreExcel] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public virtual DateTime? CreateTime { get; set; } | ||||
| 
 | ||||
|     /// <summary> | ||||
| @@ -53,13 +68,16 @@ public abstract class BaseEntity : PrimaryKeyEntity | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnDescription = "创建人", IsOnlyIgnoreUpdate = true, IsNullable = true)] | ||||
|     [IgnoreExcel] | ||||
|     public virtual string CreateUser { get; set; } | ||||
|     [NotNull] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public virtual string? CreateUser { get; set; } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 创建者Id | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnDescription = "创建者Id", IsOnlyIgnoreUpdate = true, IsNullable = true)] | ||||
|     [IgnoreExcel] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public virtual long CreateUserId { get; set; } | ||||
| 
 | ||||
|     /// <summary> | ||||
| @@ -67,6 +85,7 @@ public abstract class BaseEntity : PrimaryKeyEntity | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnDescription = "软删除", IsNullable = true)] | ||||
|     [IgnoreExcel] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public virtual bool IsDelete { get; set; } = false; | ||||
| 
 | ||||
|     /// <summary> | ||||
| @@ -74,6 +93,7 @@ public abstract class BaseEntity : PrimaryKeyEntity | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnDescription = "更新时间", IsOnlyIgnoreInsert = true, IsNullable = true)] | ||||
|     [IgnoreExcel] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public virtual DateTime? UpdateTime { get; set; } | ||||
| 
 | ||||
|     /// <summary> | ||||
| @@ -81,19 +101,21 @@ public abstract class BaseEntity : PrimaryKeyEntity | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnDescription = "更新人", IsOnlyIgnoreInsert = true, IsNullable = true)] | ||||
|     [IgnoreExcel] | ||||
|     public virtual string UpdateUser { get; set; } | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public virtual string? UpdateUser { get; set; } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 修改者Id | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnDescription = "修改者Id", IsOnlyIgnoreInsert = true, IsNullable = true)] | ||||
|     [IgnoreExcel] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public virtual long? UpdateUserId { get; set; } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 排序码 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "排序码", IsNullable = false)] | ||||
|     [IgnoreExcel] | ||||
|     public virtual int SortCode { get; set; } | ||||
| } | ||||
|     [SugarColumn(ColumnDescription = "排序码", IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = false, DefaultSort = true, Sortable = true, DefaultSortOrder = SortOrder.Asc)] | ||||
|     public int? SortCode { get; set; } | ||||
| } | ||||
| @@ -1,46 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 配置 | ||||
| ///</summary> | ||||
| [SugarTable("sys_config", TableDescription = "配置表")] | ||||
| [Tenant(SqlSugarConst.DB_Admin)] | ||||
| public class SysConfig : BaseEntity | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 分类 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "分类", Length = 200)] | ||||
|     public virtual string Category { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 配置键 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "配置键", Length = 200)] | ||||
|     [DataTable(Order = 1, IsShow = true, Sortable = true)] | ||||
|     public virtual string ConfigKey { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 配置值 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "配置值", ColumnDataType = StaticConfig.CodeFirst_BigString)] | ||||
|     [DataTable(Order = 2, IsShow = true, Sortable = true, CellClass = " table-text-truncate ")] | ||||
|     public virtual string ConfigValue { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 备注 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "备注", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 4, IsShow = true)] | ||||
|     public string Remark { get; set; } | ||||
| } | ||||
							
								
								
									
										63
									
								
								src/ThingsGateway.Admin.Application/Entity/SysDict.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,63 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| using System.ComponentModel.DataAnnotations; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| [SugarTable("sys_dict", TableDescription = "字典表")] | ||||
| [Tenant(SqlSugarConst.DB_Admin)] | ||||
| public class SysDict : BaseEntity | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 类型 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "类型", Length = 200)] | ||||
|     [AutoGenerateColumn(Ignore = true, Filterable = true, Sortable = true)] | ||||
|     public virtual DictTypeEnum DictType { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 分类 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "分类", Length = 200)] | ||||
|     [Required] | ||||
|     [AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)] | ||||
|     public string Category { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 名称 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "名称", Length = 200)] | ||||
|     [Required] | ||||
|     [AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)] | ||||
|     public virtual string Name { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 代码 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "代码", ColumnDataType = StaticConfig.CodeFirst_BigString)] | ||||
|     [Required] | ||||
|     [AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)] | ||||
|     public virtual string Code { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 描述 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "描述", Length = 200, IsNullable = true)] | ||||
|     public string Remark { get; set; } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,6 +9,13 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -15,54 +23,118 @@ namespace ThingsGateway.Admin.Application; | ||||
| ///</summary> | ||||
| [SugarTable("sys_operatelog", TableDescription = "操作日志表")] | ||||
| [Tenant(SqlSugarConst.DB_Log)] | ||||
| public class SysOperateLog : SysVisitLog | ||||
| public class SysOperateLog | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 日志分类 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "日志分类", Length = 200)] | ||||
|     [AutoGenerateColumn(Order = 1, Filterable = true, Sortable = true)] | ||||
|     public LogCateGoryEnum Category { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 日志名称 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "日志名称", Length = 200)] | ||||
|     [AutoGenerateColumn(Order = 2, Filterable = true, Sortable = true)] | ||||
|     public string Name { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 类名称 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "类名称", Length = 200)] | ||||
|     [DataTable(Order = 21, IsShow = true, Sortable = true, DefaultFilter = true, CellClass = " table-text-truncate ")] | ||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||
|     public string ClassName { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 具体消息 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "具体消息", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)] | ||||
|     [DataTable(Order = 22, IsShow = true, Sortable = true, DefaultFilter = false, CellClass = " table-text-truncate ")] | ||||
|     public string ExeMessage { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 方法名称 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "方法名称", Length = 200)] | ||||
|     [DataTable(Order = 23, IsShow = true, Sortable = true, DefaultFilter = true, CellClass = " table-text-truncate ")] | ||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||
|     public string MethodName { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 请求参数 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "请求参数", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)] | ||||
|     [DataTable(Order = 24, IsShow = true, Sortable = true, DefaultFilter = false, CellClass = " table-text-truncate ")] | ||||
|     public string ParamJson { get; set; } | ||||
|     [AutoGenerateColumn(ShowTips = true, Filterable = true, Sortable = true)] | ||||
|     public string? ParamJson { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 请求方式 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "请求方式", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 25, IsShow = true, Sortable = true, DefaultFilter = true, CellClass = " table-text-truncate ")] | ||||
|     public string ReqMethod { get; set; } | ||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||
|     public string? ReqMethod { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 请求地址 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "请求地址", ColumnDataType = StaticConfig.CodeFirst_BigString)] | ||||
|     [DataTable(Order = 26, IsShow = true, Sortable = true, DefaultFilter = true, CellClass = " table-text-truncate ")] | ||||
|     public string ReqUrl { get; set; } | ||||
|     [SugarColumn(ColumnDescription = "请求地址", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||
|     public string? ReqUrl { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 返回结果 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "返回结果", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)] | ||||
|     [DataTable(Order = 27, IsShow = true, Sortable = true, DefaultFilter = false, CellClass = " table-text-truncate ")] | ||||
|     public string ResultJson { get; set; } | ||||
|     [AutoGenerateColumn(ShowTips = true, Filterable = true, Sortable = true)] | ||||
|     public string? ResultJson { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 具体消息 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "具体消息", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)] | ||||
|     [AutoGenerateColumn(ShowTips = true, Filterable = true, Sortable = true)] | ||||
|     public string? ExeMessage { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 执行状态 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "执行状态", Length = 200)] | ||||
|     [AutoGenerateColumn(Filterable = true, Sortable = true)] | ||||
|     public bool ExeStatus { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 操作账号 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "操作账号", Length = 200, IsNullable = true)] | ||||
|     [AutoGenerateColumn(Filterable = true, Sortable = true)] | ||||
|     public string? OpAccount { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 操作浏览器 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "操作浏览器", Length = 200)] | ||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||
|     public string OpBrowser { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 操作ip | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "操作ip", Length = 200)] | ||||
|     [AutoGenerateColumn(Filterable = true, Sortable = true)] | ||||
|     public string? OpIp { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 操作系统 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "操作系统", Length = 200)] | ||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||
|     public string OpOs { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 操作时间 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "操作时间")] | ||||
|     [AutoGenerateColumn(Visible = true, DefaultSort = true, Sortable = true, DefaultSortOrder = SortOrder.Desc)] | ||||
|     public DateTime OpTime { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 验证Id | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "验证Id")] | ||||
|     [IgnoreExcel] | ||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||
|     public long VerificatId { get; set; } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,6 +9,11 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -21,7 +27,7 @@ public class SysRelation : PrimaryKeyEntity | ||||
|     /// 分类 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "分类", Length = 200)] | ||||
|     public string Category { get; set; } | ||||
|     public RelationCategoryEnum Category { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 对象ID | ||||
| @@ -33,5 +39,5 @@ public class SysRelation : PrimaryKeyEntity | ||||
|     /// 目标ID | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "目标ID", IsNullable = true)] | ||||
|     public string TargetId { get; set; } | ||||
|     public string? TargetId { get; set; } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,7 +9,18 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.ComponentModel; | ||||
|  | ||||
|  | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Routing; | ||||
|  | ||||
| using Newtonsoft.Json; | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| using System.ComponentModel.DataAnnotations; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| @@ -22,91 +34,72 @@ public class SysResource : BaseEntity | ||||
|     /// <summary> | ||||
|     /// 父id | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "父id", IsNullable = true)] | ||||
|     public virtual long? ParentId { get; set; } | ||||
|     [SugarColumn(ColumnDescription = "父id")] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public virtual long ParentId { get; set; } = 0; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 模块 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "模块")] | ||||
|     [AutoGenerateColumn(Visible = false, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false, Searchable = true)] | ||||
|     public virtual long Module { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 标题 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "标题", Length = 200)] | ||||
|     [DataTable(Order = 1, IsShow = true, Sortable = true)] | ||||
|     [Required] | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true, Searchable = true)] | ||||
|     public virtual string Title { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 编码 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "编码", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 5, IsShow = true, Sortable = true, DefaultFilter = true)] | ||||
|     public virtual string Code { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 分类 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "分类", Length = 200)] | ||||
|     [DataTable(Order = 4, IsShow = true, Sortable = true, DefaultFilter = true)] | ||||
|     public string Category { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 菜单类型 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "菜单类型", ColumnDataType = "varchar(50)")] | ||||
|     [DataTable(Order = 4, IsShow = true, Sortable = true, DefaultFilter = true)] | ||||
|     public virtual MenuTypeEnum MenuType { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 路径 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "路径", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 3, IsShow = true, Sortable = true)] | ||||
|     public virtual string Href { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 图标 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "图标", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 2, IsShow = true, Sortable = true)] | ||||
|     public virtual string Icon { get; set; } | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = false, Filterable = false)] | ||||
|     public virtual string? Icon { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否隐藏 | ||||
|     /// 编码 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "编码", Length = 200)] | ||||
|     [Required] | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)] | ||||
|     public virtual string Code { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 分类 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "分类")] | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)] | ||||
|     public ResourceCategoryEnum Category { get; set; } = ResourceCategoryEnum.Menu; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 目标类型 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "目标类型", IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)] | ||||
|     public virtual TargetEnum? Target { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 菜单匹配类型 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnDescription = "隐藏", Length = 200)] | ||||
|     [DataTable(Order = 2, IsShow = true, Sortable = true)] | ||||
|     public bool Hidden { get; set; } = false; | ||||
|     [SugarColumn(ColumnDescription = "菜单匹配类型", IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)] | ||||
|     public virtual NavLinkMatch? NavLinkMatch { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 路径 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "路径", Length = 200, IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true, Searchable = true)] | ||||
|     public virtual string Href { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 子节点 | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsIgnore = true)] | ||||
|     public List<SysResource> Children { get; set; } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// 链接跳转类型 | ||||
| /// </summary> | ||||
| public enum MenuTypeEnum | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 目录 | ||||
|     /// </summary> | ||||
|     [Description("目录")] | ||||
|     CATALOG = 0, | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 组件 | ||||
|     /// </summary> | ||||
|     [Description("组件")] | ||||
|     MENU, | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 内链 | ||||
|     /// </summary> | ||||
|     [Description("内链")] | ||||
|     IFRAME, | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 外链 | ||||
|     /// </summary> | ||||
|     [Description("外链")] | ||||
|     LINK, | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public List<SysResource>? Children { get; set; } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,6 +9,15 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| using System.ComponentModel.DataAnnotations; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -21,35 +31,26 @@ public class SysRole : BaseEntity | ||||
|     /// 编码 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "编码", Length = 200)] | ||||
|     [DataTable(Order = 2, IsShow = true, Sortable = true)] | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)] | ||||
|     public string Code { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 名称 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "名称", Length = 200)] | ||||
|     [DataTable(Order = 1, IsShow = true, Sortable = true)] | ||||
|     [Required] | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)] | ||||
|     public virtual string Name { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 分类 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "分类", Length = 200, IsNullable = false)] | ||||
|     [DataTable(Order = 1, IsShow = true, Sortable = true)] | ||||
|     public virtual string Category { get; set; } | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)] | ||||
|     public virtual RoleCategoryEnum Category { get; set; } | ||||
|  | ||||
|     public override int GetHashCode() | ||||
|     { | ||||
|         return Id.GetHashCode(); | ||||
|     } | ||||
|  | ||||
|     public override bool Equals(object? obj) | ||||
|     { | ||||
|         if (obj == null || !(obj is UserSelectorOutput)) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return Id == ((UserSelectorOutput)obj).Id; | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,6 +9,17 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| using System.ComponentModel.DataAnnotations; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -17,111 +29,159 @@ namespace ThingsGateway.Admin.Application; | ||||
| [Tenant(SqlSugarConst.DB_Admin)] | ||||
| public class SysUser : BaseEntity | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 头像 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "头像", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = false, Filterable = false)] | ||||
|     [AdaptIgnore] | ||||
|     public virtual string? Avatar { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 账号 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "账号", Length = 200)] | ||||
|     [DataTable(Order = 1, IsShow = true, Sortable = true)] | ||||
|     [Required] | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)] | ||||
|     public virtual string Account { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 按钮码集合 | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsIgnore = true)] | ||||
|     public List<string> ButtonCodeList { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 邮箱 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "邮箱", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 2, IsShow = true, Sortable = true)] | ||||
|     public string Email { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 上次登录设备 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "上次登录设备", IsNullable = true)] | ||||
|     [DataTable(Order = 3, IsShow = true, Sortable = true, DefaultFilter = true)] | ||||
|     public string LastLoginDevice { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 上次登录ip | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "上次登录ip", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 4, IsShow = true, Sortable = true, DefaultFilter = true)] | ||||
|     public string LastLoginIp { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 上次登录时间 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "上次登录时间", IsNullable = true)] | ||||
|     [DataTable(Order = 5, IsShow = true, Sortable = true, DefaultFilter = true)] | ||||
|     public DateTime? LastLoginTime { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 最新登录设备 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "最新登录设备", IsNullable = true)] | ||||
|     [DataTable(Order = 6, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public string LatestLoginDevice { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 最新登录ip | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "最新登录ip", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 7, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public string LatestLoginIp { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 最新登录时间 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "最新登录时间", IsNullable = true)] | ||||
|     [DataTable(Order = 8, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public DateTime? LatestLoginTime { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 密码 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "密码", ColumnDataType = StaticConfig.CodeFirst_BigString)] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public string Password { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 权限码集合 | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsIgnore = true)] | ||||
|     public List<string> PermissionCodeList { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 手机 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "手机", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 2, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public string Phone { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 数据范围集合 | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsIgnore = true)] | ||||
|     public List<DataScope> DataScopeList { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 角色码集合 | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsIgnore = true)] | ||||
|     public List<string> RoleCodeList { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 角色ID集合 | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsIgnore = true)] | ||||
|     public List<long> RoleIdList { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否启用 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "是否启用")] | ||||
|     [DataTable(Order = 2, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public bool UserStatus { get; set; } | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)] | ||||
|     public bool Status { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 手机 | ||||
|     /// 这里使用了SM4自动加密解密 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "手机", Length = 200, IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)] | ||||
|     public string? Phone { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 邮箱 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "邮箱", Length = 200, IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)] | ||||
|     public string? Email { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 上次登录ip | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "上次登录ip", Length = 200, IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)] | ||||
|     public string? LastLoginIp { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 上次登录设备 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "上次登录设备", IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)] | ||||
|     public string? LastLoginDevice { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 上次登录时间 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "上次登录时间", IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)] | ||||
|     public DateTime? LastLoginTime { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 上次登录地点 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "上次登录地点", Length = 200, IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)] | ||||
|     public string LastLoginAddress { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 最新登录ip | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "最新登录ip", Length = 200, IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)] | ||||
|     public string? LatestLoginIp { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 最新登录时间 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "最新登录时间", IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)] | ||||
|     public DateTime? LatestLoginTime { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 最新登录设备 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "最新登录设备", IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)] | ||||
|     public string? LatestLoginDevice { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 最新登录地点 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "最新登录地点", Length = 200, IsNullable = true)] | ||||
|     [AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)] | ||||
|     public string LatestLoginAddress { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 默认模块 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "默认模块")] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public long DefaultModule { get; set; } | ||||
|  | ||||
|     #region other | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 按钮码集合 | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsIgnore = true)] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public Dictionary<string, List<string>> ButtonCodeList { get; set; } = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 权限码集合 | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsIgnore = true)] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public IEnumerable<string> PermissionCodeList { get; set; } = Enumerable.Empty<string>(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 角色码集合 | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsIgnore = true)] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public IEnumerable<string> RoleCodeList { get; set; } = Enumerable.Empty<string>(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 角色ID集合 | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsIgnore = true)] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public IEnumerable<long> RoleIdList { get; set; } = Enumerable.Empty<long>(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 数据范围集合 | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsIgnore = true)] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public IEnumerable<DataScope> DataScopeList { get; set; } = Enumerable.Empty<DataScope>(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 模块集合 | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsIgnore = true)] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public IEnumerable<SysResource> ModuleList { get; set; } = Enumerable.Empty<SysResource>(); | ||||
|  | ||||
|     #endregion other | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
|   | ||||
| @@ -1,83 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 访问日志表 | ||||
| ///</summary> | ||||
| [SugarTable("sys_visitlog", TableDescription = "访问日志表")] | ||||
| [Tenant(SqlSugarConst.DB_Log)] | ||||
| public class SysVisitLog : PrimaryIdEntity | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 日志分类 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "日志分类", Length = 200)] | ||||
|     [DataTable(Order = 1, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public string Category { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 执行状态 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "执行状态", Length = 200)] | ||||
|     [DataTable(Order = 3, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public string ExeStatus { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 日志名称 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "日志名称", Length = 200)] | ||||
|     [DataTable(Order = 2, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public string Name { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 操作人账号 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "操作人账号", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 4, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public string OpAccount { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 操作浏览器 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "操作浏览器", Length = 200)] | ||||
|     [DataTable(Order = 5, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public string OpBrowser { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 操作ip | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "操作ip", Length = 200)] | ||||
|     [DataTable(Order = 6, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public string OpIp { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 操作系统 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "操作系统", Length = 200)] | ||||
|     [DataTable(Order = 7, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public string OpOs { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 操作时间 | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "操作时间")] | ||||
|     [DataTable(Order = 8, IsShow = true, Sortable = true, DefaultFilter = false)] | ||||
|     public DateTime OpTime { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 验证Id | ||||
|     ///</summary> | ||||
|     [SugarColumn(ColumnDescription = "验证Id")] | ||||
|     [IgnoreExcel] | ||||
|     [DataTable(Order = 9, IsShow = true, Sortable = true, DefaultFilter = true)] | ||||
|     public long VerificatId { get; set; } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,9 +9,10 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| using System.ComponentModel; | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 登录设备类型枚举 | ||||
| @@ -20,18 +22,10 @@ public enum AuthDeviceTypeEnum | ||||
|     /// <summary> | ||||
|     /// PC端 | ||||
|     /// </summary> | ||||
|     [Description("PC")] | ||||
|     PC, | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 移动端 | ||||
|     /// </summary> | ||||
|     [Description("APP")] | ||||
|     APP, | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Api | ||||
|     /// </summary> | ||||
|     [Description("Api")] | ||||
|     Api, | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,20 +9,23 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Components; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 键值表示 | ||||
| /// 字典表类型 | ||||
| /// </summary> | ||||
| public class StringFilters | ||||
| public enum DictTypeEnum | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 键 | ||||
|     /// 系统使用 | ||||
|     /// </summary> | ||||
|     public string Key { get; set; } | ||||
|     System, | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 值 | ||||
|     /// 用户自定义 | ||||
|     /// </summary> | ||||
|     public string Value { get; set; } | ||||
|     Define, | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,9 +9,15 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| public interface ISugarService | ||||
| public enum LogCateGoryEnum | ||||
| { | ||||
|     public SqlSugarClient NewContent { get; set; } | ||||
|     Login, | ||||
|     Logout, | ||||
|     Operate, | ||||
|     Exception | ||||
| } | ||||
| @@ -0,0 +1,30 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| public enum RelationCategoryEnum | ||||
| { | ||||
|     UserHasRole, | ||||
|     UserHasResource, | ||||
|     UserHasPermission, | ||||
|     UserHasOpenApiPermission, | ||||
|     UserHasModule, | ||||
|     UserDefaultRazor, | ||||
|     UserWorkbenchData, | ||||
|     RoleHasResource, | ||||
|     RoleHasPermission, | ||||
|     RoleHasOpenApiPermission, | ||||
|     RoleHasModule, | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,6 +9,14 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| global using Microsoft.Extensions.DependencyInjection; | ||||
| 
 | ||||
| global using NewLife.Caching; | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| public enum ResourceCategoryEnum | ||||
| { | ||||
|     Module, | ||||
|     Menu, | ||||
|     Button, | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,8 +9,13 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application.Services.System.Relation.Dto; | ||||
| 
 | ||||
| public class RelationOutput | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| public enum RoleCategoryEnum | ||||
| { | ||||
|     Global, | ||||
|     Api, | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/ThingsGateway.Admin.Application/Enum/TargetEnum.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,23 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| public enum TargetEnum | ||||
| { | ||||
|     _self, | ||||
|     _blank, | ||||
|     _parent, | ||||
|     _top | ||||
| } | ||||
| @@ -1,117 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 认证模块事件总线 | ||||
| /// </summary> | ||||
| public class AuthEventSubscriber : IEventSubscriber, ISingleton | ||||
| { | ||||
|     private readonly ISimpleCacheService _simpleCacheService; | ||||
|     public IServiceProvider _services { get; } | ||||
|     private readonly SqlSugarScope _db; | ||||
|  | ||||
|     public AuthEventSubscriber(ISimpleCacheService simpleCacheService, IServiceProvider services) | ||||
|     { | ||||
|         _db = DbContext.Db; | ||||
|         _simpleCacheService = simpleCacheService; | ||||
|         _services = services; | ||||
|     } | ||||
|  | ||||
|     /// <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; | ||||
|  | ||||
|         #region 登录/密码策略 | ||||
|  | ||||
|         var key = SystemConst.Cache_LoginErrorCount + sysUser.Account;//获取登录错误次数Key值 | ||||
|         _simpleCacheService.Remove(key);//移除登录错误次数 | ||||
|  | ||||
|         // 创建新的作用域 | ||||
|         using var scope = _services.CreateScope(); | ||||
|         // 解析服务 | ||||
|         var configService = scope.ServiceProvider.GetRequiredService<IConfigService>(); | ||||
|         var loginPolicy = await configService.GetListByCategoryAsync(CateGoryConst.Config_PWD_POLICY);//获取密码策略 | ||||
|         //获取用户verificat列表 | ||||
|         var tokenInfos = UserTokenCacheUtil.HashGetOne(sysUser.Id); | ||||
|         var userToken = tokenInfos.Where(it => it.Id == loginEvent.VerificatId).FirstOrDefault(); | ||||
|  | ||||
|         #endregion 登录/密码策略 | ||||
|  | ||||
|         #region 重新赋值属性,设置本次登录信息为最新的信息 | ||||
|  | ||||
|         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).UpdateColumns(it => new | ||||
|         { | ||||
|             it.LastLoginDevice, | ||||
|             it.LastLoginIp, | ||||
|             it.LastLoginTime, | ||||
|             it.LatestLoginDevice, | ||||
|             it.LatestLoginIp, | ||||
|             it.LatestLoginTime, | ||||
|         }).ExecuteCommandAsync() > 0) | ||||
|             _simpleCacheService.HashAdd(SystemConst.Cache_SysUser, sysUser.Id.ToString(), sysUser);//更新Redis信息 | ||||
|  | ||||
|         await Task.CompletedTask; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 登出事件 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <returns></returns> | ||||
|     [EventSubscribe(EventSubscriberConst.LoginOut)] | ||||
|     public async Task LoginOut(EventHandlerExecutingContext context) | ||||
|     { | ||||
|         _ = (LoginEvent)context.Source.Payload;//获取参数 | ||||
|         await Task.CompletedTask; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取通知服务 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private INoticeService GetNoticeService() | ||||
|     { | ||||
|         var noticeService = _services.GetService<INoticeService>();//获取服务 | ||||
|         return noticeService; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     ///   延迟执行 | ||||
|     /// </summary> | ||||
|     /// <param name="millisecondsDelay">毫秒</param> | ||||
|     /// <param name="actionToExecute">方法</param> | ||||
|     private async Task DelayedExecutionAsync(int millisecondsDelay, Action actionToExecute) | ||||
|     { | ||||
|         // 延迟指定的时间 | ||||
|         await Task.Delay(millisecondsDelay); | ||||
|  | ||||
|         // 执行目标方法 | ||||
|         actionToExecute.Invoke(); | ||||
|     } | ||||
| } | ||||
| @@ -1,54 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 事件总线常量 | ||||
| /// </summary> | ||||
| public class EventSubscriberConst | ||||
| { | ||||
|     #region AuthEventSubscriber | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 登录 | ||||
|     /// </summary> | ||||
|     public const string Login = "登录"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 登出 | ||||
|     /// </summary> | ||||
|     public const string LoginOut = "登出"; | ||||
|  | ||||
|     #endregion AuthEventSubscriber | ||||
|  | ||||
|     #region UserEventSubscriber | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 清除用户缓存 | ||||
|     /// </summary> | ||||
|     public const string ClearUserCache = "清除用户缓存"; | ||||
|  | ||||
|     #endregion UserEventSubscriber | ||||
|  | ||||
|     #region NoticeEventSubscriber | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 通知用户下线 | ||||
|     /// </summary> | ||||
|     public const string UserLoginOut = "通知用户下线"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 新消息 | ||||
|     /// </summary> | ||||
|     public const string NewMessage = "新消息"; | ||||
|  | ||||
|     #endregion NoticeEventSubscriber | ||||
| } | ||||
| @@ -1,86 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 通知事件总线 | ||||
| /// </summary> | ||||
| public class NoticeEventSubsciber : IEventSubscriber, ISingleton | ||||
| { | ||||
|     private readonly ISimpleCacheService _simpleCacheService; | ||||
|  | ||||
|     public IServiceProvider _service { get; } | ||||
|     private readonly SqlSugarScope _db; | ||||
|  | ||||
|     public NoticeEventSubsciber(ISimpleCacheService simpleCacheService, IServiceProvider service) | ||||
|     { | ||||
|         _db = DbContext.Db; | ||||
|         _simpleCacheService = simpleCacheService; | ||||
|         _service = service; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 通知用户下线事件 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <returns></returns> | ||||
|     [EventSubscribe(EventSubscriberConst.UserLoginOut)] | ||||
|     public async Task UserLoginOut(EventHandlerExecutingContext context) | ||||
|     { | ||||
|         var loginEvent = (UserLoginOutEvent)context.Source.Payload;//获取参数 | ||||
|         //客户端ID列表 | ||||
|         var clientIds = new List<string>(); | ||||
|         //遍历verificat列表获取客户端ID列表 | ||||
|         loginEvent?.VerificatInfos?.ForEach(it => | ||||
|         { | ||||
|             clientIds.AddRange(it.ClientIds); | ||||
|         }); | ||||
|         await GetNoticeService().UserLoginOut(loginEvent.UserId, clientIds, loginEvent.Message);//发送消息 | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 有新的消息 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <returns></returns> | ||||
|     [EventSubscribe(EventSubscriberConst.NewMessage)] | ||||
|     public async Task NewMessage(EventHandlerExecutingContext context) | ||||
|     { | ||||
|         var newMessageEvent = (NewMessageEvent)context.Source.Payload;//获取参数 | ||||
|  | ||||
|         var clientIds = new List<string>(); | ||||
|         //获取用户verificat列表 | ||||
|         var verificatInfos = UserTokenCacheUtil.HashGet(newMessageEvent.UserIds.ToArray()); | ||||
|         verificatInfos.ForEach(it => | ||||
|         { | ||||
|             if (it != null) | ||||
|             { | ||||
|                 it = it.Where(it => it.VerificatTimeout > DateTime.Now).ToList();//去掉登录超时的 | ||||
|                 //遍历verificat | ||||
|                 it.ForEach(it => | ||||
|                 { | ||||
|                     clientIds.AddRange(it.ClientIds);//添加到客户端ID列表 | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|         await GetNoticeService().NewMesage(newMessageEvent.UserIds, clientIds, newMessageEvent.Message);//发送消息 | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取通知服务 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private INoticeService GetNoticeService() | ||||
|     { | ||||
|         var noticeService = _service.CreateScope().ServiceProvider.GetService<INoticeService>();//获取服务 | ||||
|         return noticeService; | ||||
|     } | ||||
| } | ||||
| @@ -1,50 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 用户模块事件总线 | ||||
| /// </summary> | ||||
| public class UserEventSubscriber : IEventSubscriber, ISingleton | ||||
| { | ||||
|     private readonly IServiceProvider _services; | ||||
|  | ||||
|     public UserEventSubscriber(IServiceProvider services) | ||||
|     { | ||||
|         this._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).ToList();//用户ID列表 | ||||
|             // 解析用户服务 | ||||
|             var userService = scope.ServiceProvider.GetRequiredService<ISysUserService>(); | ||||
|             //从redis中删除 | ||||
|             userService.DeleteUserFromRedis(userIds); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,23 +9,25 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| using TouchSocket.Core; | ||||
| 
 | ||||
| namespace ThingsGateway.Demo; | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <inheritdoc/> | ||||
| public class MenuConfigs : AppConfigBase | ||||
| public class UserFriendlyException : Exception | ||||
| { | ||||
|     public static MenuConfigs Default; | ||||
| 
 | ||||
|     static MenuConfigs() | ||||
|     { | ||||
|         Default = AppConfigBase.GetNewDefault<MenuConfigs>(); | ||||
|     } | ||||
| 
 | ||||
|     public List<NavItem> NavItems { get; set; } = new(); | ||||
| 
 | ||||
|     public MenuConfigs() : base(AppContext.BaseDirectory.CombinePath("MenuConfig.json")) | ||||
|     public UserFriendlyException() | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public UserFriendlyException(string? message) : base(message) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public UserFriendlyException(string? message, Exception? innerException) : base(message, innerException) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public bool? IsValidationException { get; set; } | ||||
| } | ||||
| @@ -0,0 +1,78 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.AspNetCore.Mvc.Filters; | ||||
|  | ||||
| namespace Microsoft.Extensions.DependencyInjection; | ||||
|  | ||||
| /// <summary> | ||||
| /// ASP.NET Core 服务拓展类 | ||||
| /// </summary> | ||||
| public static class AspNetCoreBuilderServiceCollectionExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 注册 Mvc 过滤器 | ||||
|     /// </summary> | ||||
|     /// <typeparam name="TFilter"></typeparam> | ||||
|     /// <param name="mvcBuilder"></param> | ||||
|     /// <param name="configure"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IMvcBuilder AddMvcFilter<TFilter>(this IMvcBuilder mvcBuilder, Action<MvcOptions> configure = default) | ||||
|         where TFilter : IFilterMetadata | ||||
|     { | ||||
|         mvcBuilder.Services.AddMvcFilter<TFilter>(configure); | ||||
|  | ||||
|         return mvcBuilder; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 注册 Mvc 过滤器 | ||||
|     /// </summary> | ||||
|     /// <typeparam name="TFilter"></typeparam> | ||||
|     /// <param name="services"></param> | ||||
|     /// <param name="configure"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IServiceCollection AddMvcFilter<TFilter>(this IServiceCollection services, Action<MvcOptions> configure = default) | ||||
|         where TFilter : IFilterMetadata | ||||
|     { | ||||
|         services.Configure<MvcOptions>(options => | ||||
|         { | ||||
|             options.Filters.Add<TFilter>(); | ||||
|  | ||||
|             // 其他额外配置 | ||||
|             configure?.Invoke(options); | ||||
|         }); | ||||
|  | ||||
|         return services; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 注册 Mvc 过滤器 | ||||
|     /// </summary> | ||||
|     /// <param name="services"></param> | ||||
|     /// <param name="filter"></param> | ||||
|     /// <param name="configure"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IServiceCollection AddMvcFilter(this IServiceCollection services, IFilterMetadata filter, Action<MvcOptions> configure = default) | ||||
|     { | ||||
|         services.Configure<MvcOptions>(options => | ||||
|         { | ||||
|             options.Filters.Add(filter); | ||||
|  | ||||
|             // 其他额外配置 | ||||
|             configure?.Invoke(options); | ||||
|         }); | ||||
|  | ||||
|         return services; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,43 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.AspNetCore.Mvc.Filters; | ||||
|  | ||||
| namespace Microsoft.AspNetCore.Authorization; | ||||
|  | ||||
| /// <summary> | ||||
| /// 授权处理上下文拓展类 | ||||
| /// </summary> | ||||
| public static class AuthorizationHandlerContextExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 获取当前 HttpContext 上下文 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DefaultHttpContext GetCurrentHttpContext(this AuthorizationHandlerContext context) | ||||
|     { | ||||
|         DefaultHttpContext? httpContext; | ||||
|  | ||||
|         // 获取 httpContext 对象 | ||||
|         if (context.Resource is AuthorizationFilterContext filterContext) httpContext = (DefaultHttpContext)filterContext.HttpContext; | ||||
|         else if (context.Resource is DefaultHttpContext defaultHttpContext) httpContext = defaultHttpContext; | ||||
|         else httpContext = null; | ||||
|  | ||||
|         return httpContext; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,191 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using Microsoft.AspNetCore.Mvc.Controllers; | ||||
|  | ||||
| using System.Text; | ||||
|  | ||||
| namespace Microsoft.AspNetCore.Http; | ||||
|  | ||||
| /// <summary> | ||||
| /// Http 拓展类 | ||||
| /// </summary> | ||||
| public static class HttpContextExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 获取 Action 特性 | ||||
|     /// </summary> | ||||
|     /// <typeparam name="TAttribute"></typeparam> | ||||
|     /// <param name="httpContext"></param> | ||||
|     /// <returns></returns> | ||||
|     public static TAttribute GetMetadata<TAttribute>(this HttpContext httpContext) | ||||
|         where TAttribute : class | ||||
|     { | ||||
|         return httpContext.GetEndpoint()?.Metadata?.GetMetadata<TAttribute>(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取 控制器/Action 描述器 | ||||
|     /// </summary> | ||||
|     /// <param name="httpContext"></param> | ||||
|     /// <returns></returns> | ||||
|     public static ControllerActionDescriptor GetControllerActionDescriptor(this HttpContext httpContext) | ||||
|     { | ||||
|         return httpContext.GetEndpoint()?.Metadata?.FirstOrDefault(u => u is ControllerActionDescriptor) as ControllerActionDescriptor; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置规范化文档自动登录 | ||||
|     /// </summary> | ||||
|     /// <param name="httpContext"></param> | ||||
|     /// <param name="accessToken"></param> | ||||
|     public static void SigninToSwagger(this HttpContext httpContext, string accessToken) | ||||
|     { | ||||
|         // 设置 Swagger 刷新自动授权 | ||||
|         httpContext.Response.Headers["access-token"] = accessToken; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置规范化文档退出登录 | ||||
|     /// </summary> | ||||
|     /// <param name="httpContext"></param> | ||||
|     public static void SignoutToSwagger(this HttpContext httpContext) | ||||
|     { | ||||
|         httpContext.Response.Headers["access-token"] = "invalid_token"; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置响应头 Tokens | ||||
|     /// </summary> | ||||
|     /// <param name="httpContext"></param> | ||||
|     /// <param name="accessToken"></param> | ||||
|     /// <param name="refreshToken"></param> | ||||
|     public static void SetTokensOfResponseHeaders(this HttpContext httpContext, string accessToken, string? refreshToken = null) | ||||
|     { | ||||
|         httpContext.Response.Headers["access-token"] = accessToken; | ||||
|         if (!string.IsNullOrWhiteSpace(refreshToken)) | ||||
|         { | ||||
|             httpContext.Response.Headers["x-access-token"] = refreshToken; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取本机 IPv4地址 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string GetLocalIpAddressToIPv4(this HttpContext context) | ||||
|     { | ||||
|         return context.Connection.LocalIpAddress?.MapToIPv4()?.ToString(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取本机 IPv6地址 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string GetLocalIpAddressToIPv6(this HttpContext context) | ||||
|     { | ||||
|         return context.Connection.LocalIpAddress?.MapToIPv6()?.ToString(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取远程 IPv4地址 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string GetRemoteIpAddressToIPv4(this HttpContext context) | ||||
|     { | ||||
|         return context.Connection.RemoteIpAddress?.MapToIPv4()?.ToString(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取远程 IPv6地址 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string GetRemoteIpAddressToIPv6(this HttpContext context) | ||||
|     { | ||||
|         return context.Connection.RemoteIpAddress?.MapToIPv6()?.ToString(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取完整请求地址 | ||||
|     /// </summary> | ||||
|     /// <param name="request"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string GetRequestUrlAddress(this HttpRequest request) | ||||
|     { | ||||
|         return new StringBuilder() | ||||
|                 .Append(request.Scheme) | ||||
|                 .Append("://") | ||||
|                 .Append(request.Host) | ||||
|                 .Append(request.PathBase) | ||||
|                 .Append(request.Path) | ||||
|                 .Append(request.QueryString) | ||||
|                 .ToString(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取来源地址 | ||||
|     /// </summary> | ||||
|     /// <param name="request"></param> | ||||
|     /// <param name="refererHeaderKey"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string GetRefererUrlAddress(this HttpRequest request, string refererHeaderKey = "Referer") | ||||
|     { | ||||
|         return request.Headers[refererHeaderKey].ToString(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 读取 Body 内容 | ||||
|     /// </summary> | ||||
|     /// <param name="httpContext"></param> | ||||
|     /// <remarks>需先在 Startup 的 Configure 中注册 app.EnableBuffering()</remarks> | ||||
|     /// <returns></returns> | ||||
|     public static async Task<string> ReadBodyContentAsync(this HttpContext httpContext) | ||||
|     { | ||||
|         if (httpContext == null) return default; | ||||
|         return await httpContext.Request.ReadBodyContentAsync(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 读取 Body 内容 | ||||
|     /// </summary> | ||||
|     /// <param name="request"></param> | ||||
|     /// <remarks>需先在 Startup 的 Configure 中注册 app.EnableBuffering()</remarks> | ||||
|     /// <returns></returns> | ||||
|     public static async Task<string> ReadBodyContentAsync(this HttpRequest request) | ||||
|     { | ||||
|         request.Body.Seek(0, SeekOrigin.Begin); | ||||
|  | ||||
|         using var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true); | ||||
|         var body = await reader.ReadToEndAsync(); | ||||
|  | ||||
|         request.Body.Seek(0, SeekOrigin.Begin); | ||||
|         return body; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 判断是否是 WebSocket 请求 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <returns></returns> | ||||
|     public static bool IsWebSocketRequest(this HttpContext context) | ||||
|     { | ||||
|         return context.WebSockets.IsWebSocketRequest || context.Request.Path == "/ws"; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,348 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
| using Microsoft.Extensions.Localization; | ||||
|  | ||||
| using System.ComponentModel; | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using System.Globalization; | ||||
| using System.Linq.Expressions; | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| public static class LocalizerExtensions | ||||
| { | ||||
|     public static PropertyInfo? GetPropertyByName(this Type type, string propertyName) => type.GetRuntimeProperties().FirstOrDefault(p => p.Name == propertyName); | ||||
|  | ||||
|     public static MethodInfo? GetMethodByName(this Type type, string methodName) => type.GetRuntimeMethods().FirstOrDefault(p => p.Name == methodName); | ||||
|  | ||||
|     public static FieldInfo? GetFieldByName(this Type type, string fieldName) => type.GetRuntimeFields().FirstOrDefault(p => p.Name == fieldName); | ||||
|  | ||||
|     private static bool IsPublic(PropertyInfo p) => p.GetMethod != null && p.SetMethod != null && p.GetMethod.IsPublic && p.SetMethod.IsPublic; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 验证整个模型时验证属性方法 | ||||
|     /// </summary> | ||||
|     /// <param name="context"></param> | ||||
|     /// <param name="results"></param> | ||||
|     public static void ValidateProperty(this ValidationContext context, List<ValidationResult> results) | ||||
|     { | ||||
|         // 获得所有可写属性 | ||||
|         var properties = context.ObjectType.GetRuntimeProperties().Where(p => IsPublic(p) && p.CanWrite && p.GetIndexParameters().Length == 0); | ||||
|         foreach (var pi in properties) | ||||
|         { | ||||
|             var fieldIdentifier = new FieldIdentifier(context.ObjectInstance, pi.Name); | ||||
|             context.DisplayName = fieldIdentifier.GetDisplayName(); | ||||
|             context.MemberName = fieldIdentifier.FieldName; | ||||
|  | ||||
|             var propertyValue = BootstrapBlazor.Components.Utility.GetPropertyValue(context.ObjectInstance, context.MemberName); | ||||
|  | ||||
|             // 验证 DataAnnotations | ||||
|             var messages = new List<ValidationResult>(); | ||||
|             // 组件进行验证 | ||||
|             ValidateDataAnnotations(propertyValue, context, messages, pi); | ||||
|             if (messages.Count > 0) | ||||
|                 results.AddRange(messages); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 通过属性设置的 DataAnnotation 进行数据验证 | ||||
|     /// </summary> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="context"></param> | ||||
|     /// <param name="results"></param> | ||||
|     /// <param name="propertyInfo"></param> | ||||
|     /// <param name="memberName"></param> | ||||
|     private static void ValidateDataAnnotations(object? value, ValidationContext context, List<ValidationResult> results, PropertyInfo propertyInfo, string? memberName = null) | ||||
|     { | ||||
|         var rules = propertyInfo.GetCustomAttributes(true).OfType<ValidationAttribute>(); | ||||
|         var metadataType = context.ObjectType.GetCustomAttribute<MetadataTypeAttribute>(false); | ||||
|         if (metadataType != null) | ||||
|         { | ||||
|             var p = metadataType.MetadataClassType.GetPropertyByName(propertyInfo.Name); | ||||
|             if (p != null) | ||||
|             { | ||||
|                 rules = rules.Concat(p.GetCustomAttributes(true).OfType<ValidationAttribute>()); | ||||
|             } | ||||
|         } | ||||
|         var displayName = context.DisplayName; | ||||
|         memberName ??= propertyInfo.Name; | ||||
|         var attributeSpan = nameof(Attribute).AsSpan(); | ||||
|         foreach (var rule in rules) | ||||
|         { | ||||
|             var result = rule.GetValidationResult(value, context); | ||||
|             if (result != null && result != ValidationResult.Success) | ||||
|             { | ||||
|                 var find = false; | ||||
|                 var ruleNameSpan = rule.GetType().Name.AsSpan(); | ||||
|                 var index = ruleNameSpan.IndexOf(attributeSpan, StringComparison.OrdinalIgnoreCase); | ||||
|                 var ruleName = ruleNameSpan[..index]; | ||||
|                 //// 通过设置 ErrorMessage 检索 | ||||
|                 //if (!context.ObjectType.Assembly.IsDynamic && !find | ||||
|                 //    && !string.IsNullOrEmpty(rule.ErrorMessage) | ||||
|                 //    && App.CreateLocalizerByType(context.ObjectType).TryGetLocalizerString(rule.ErrorMessage, out var msg)) | ||||
|                 //{ | ||||
|                 //    rule.ErrorMessage = msg; | ||||
|                 //    find = true; | ||||
|                 //} | ||||
|  | ||||
|                 //// 通过 Attribute 检索 | ||||
|                 //if (!rule.GetType().Assembly.IsDynamic && !find | ||||
|                 //    && App.CreateLocalizerByType(rule.GetType()).TryGetLocalizerString(nameof(rule.ErrorMessage), out msg)) | ||||
|                 //{ | ||||
|                 //    rule.ErrorMessage = msg; | ||||
|                 //    find = true; | ||||
|                 //} | ||||
|  | ||||
|                 // 通过 字段.规则名称 检索 | ||||
|                 if (!context.ObjectType.Assembly.IsDynamic && !find | ||||
|                     && App.CreateLocalizerByType(context.ObjectType).TryGetLocalizerString($"{memberName}.{ruleName.ToString()}", out var msg)) | ||||
|                 { | ||||
|                     rule.ErrorMessage = msg; | ||||
|                     find = true; | ||||
|                 } | ||||
|  | ||||
|                 if (!find) | ||||
|                 { | ||||
|                     rule.ErrorMessage = result.ErrorMessage; | ||||
|                 } | ||||
|                 var errorMessage = !string.IsNullOrEmpty(rule.ErrorMessage) && rule.ErrorMessage.Contains("{0}") | ||||
|                     ? rule.FormatErrorMessage(displayName) | ||||
|                     : rule.ErrorMessage; | ||||
|                 results.Add(new ValidationResult(errorMessage, new string[] { memberName })); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取指定 Type 的资源文件 | ||||
|     /// </summary> | ||||
|     /// <param name="localizer"></param> | ||||
|     /// <param name="key"></param> | ||||
|     /// <param name="text"></param> | ||||
|     /// <returns></returns> | ||||
|     public static bool TryGetLocalizerString(this IStringLocalizer localizer, string key, [MaybeNullWhen(false)] out string? text) | ||||
|     { | ||||
|         var ret = false; | ||||
|         text = null; | ||||
|         var l = localizer[key]; | ||||
|         if (l != null) | ||||
|         { | ||||
|             ret = !l.ResourceNotFound; | ||||
|             if (ret) | ||||
|             { | ||||
|                 text = l.Value; | ||||
|             } | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获得类型自身的描述信息 | ||||
|     /// </summary> | ||||
|     /// <param name="modelType"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string GetTypeDisplayName(this Type modelType) | ||||
|     { | ||||
|         string fieldName = modelType.Name; | ||||
|         var cacheKey = $"{nameof(GetTypeDisplayName)}-{CultureInfo.CurrentUICulture.Name}-{modelType.FullName}-{modelType.TypeHandle.Value}"; | ||||
|         var displayName = App.CacheService.GetOrCreate(cacheKey, entry => | ||||
|         { | ||||
|             string? dn = null; | ||||
|             // 显示名称为空时通过资源文件查找 FieldName 项 | ||||
|             var localizer = modelType.Assembly.IsDynamic ? null : App.CreateLocalizerByType(modelType); | ||||
|             var stringLocalizer = localizer?[fieldName]; | ||||
|             if (stringLocalizer is { ResourceNotFound: false }) | ||||
|             { | ||||
|                 dn = stringLocalizer.Value; | ||||
|             } | ||||
|             else if (modelType.IsEnum) | ||||
|             { | ||||
|                 var info = modelType.GetFieldByName(fieldName); | ||||
|                 if (info != null) | ||||
|                 { | ||||
|                     dn = FindDisplayAttribute(info); | ||||
|                 } | ||||
|             } | ||||
|             else if (TryGetProperty(modelType, fieldName, out var propertyInfo)) | ||||
|             { | ||||
|                 dn = FindDisplayAttribute(propertyInfo); | ||||
|             } | ||||
|  | ||||
|             return dn; | ||||
|         }, 300); | ||||
|  | ||||
|         return displayName ?? fieldName; | ||||
|  | ||||
|         string? FindDisplayAttribute(MemberInfo memberInfo) | ||||
|         { | ||||
|             // 回退查找 Display 标签 | ||||
|             var dn = memberInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name | ||||
|                 ?? memberInfo.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName | ||||
|                 ?? memberInfo.GetCustomAttribute<DescriptionAttribute>(true)?.Description; | ||||
|  | ||||
|             return dn; | ||||
|         } | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 获取 DisplayName属性名称 | ||||
|     /// </summary> | ||||
|     /// <typeparam name="T"></typeparam> | ||||
|     /// <returns></returns> | ||||
|     /// <exception cref="ArgumentNullException"></exception> | ||||
|     /// <exception cref="ArgumentException"></exception> | ||||
|     public static string Description<T>(this T item, Expression<Func<T, object>> accessor) | ||||
|     { | ||||
|         if (accessor.Body == null) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(accessor)); | ||||
|         } | ||||
|  | ||||
|         var expression = accessor.Body; | ||||
|         if (expression is UnaryExpression unaryExpression && unaryExpression.NodeType == ExpressionType.Convert && unaryExpression.Type == typeof(object)) | ||||
|         { | ||||
|             expression = unaryExpression.Operand; | ||||
|         } | ||||
|  | ||||
|         if (expression is not MemberExpression memberExpression) | ||||
|         { | ||||
|             throw new ArgumentException("Can only access properties"); | ||||
|         } | ||||
|  | ||||
|         return typeof(T).GetPropertyDisplayName(memberExpression.Member.Name) ?? memberExpression.Member.Name; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获得类型属性的描述信息 | ||||
|     /// </summary> | ||||
|     /// <param name="modelType"></param> | ||||
|     /// <param name="fieldName"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string GetPropertyDisplayName(this Type modelType, string fieldName) | ||||
|     { | ||||
|         var cacheKey = $"{nameof(GetPropertyDisplayName)}-{CultureInfo.CurrentUICulture.Name}-{modelType.FullName}-{modelType.TypeHandle.Value}-{fieldName}"; | ||||
|         var displayName = App.CacheService.GetOrCreate(cacheKey, entry => | ||||
|         { | ||||
|             string? dn = null; | ||||
|             // 显示名称为空时通过资源文件查找 FieldName 项 | ||||
|             var localizer = modelType.Assembly.IsDynamic ? null : App.CreateLocalizerByType(modelType); | ||||
|             var stringLocalizer = localizer?[fieldName]; | ||||
|             if (stringLocalizer is { ResourceNotFound: false }) | ||||
|             { | ||||
|                 dn = stringLocalizer.Value; | ||||
|             } | ||||
|             else if (modelType.IsEnum) | ||||
|             { | ||||
|                 var info = modelType.GetFieldByName(fieldName); | ||||
|                 if (info != null) | ||||
|                 { | ||||
|                     dn = FindDisplayAttribute(info); | ||||
|                 } | ||||
|             } | ||||
|             else if (TryGetProperty(modelType, fieldName, out var propertyInfo)) | ||||
|             { | ||||
|                 dn = FindDisplayAttribute(propertyInfo); | ||||
|             } | ||||
|  | ||||
|             return dn; | ||||
|         }, 300); | ||||
|  | ||||
|         return displayName ?? fieldName; | ||||
|  | ||||
|         string? FindDisplayAttribute(MemberInfo memberInfo) | ||||
|         { | ||||
|             // 回退查找 Display 标签 | ||||
|             var dn = memberInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name | ||||
|                 ?? memberInfo.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName | ||||
|                 ?? memberInfo.GetCustomAttribute<DescriptionAttribute>(true)?.Description; | ||||
|  | ||||
|             return dn; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获得方法的描述信息 | ||||
|     /// </summary> | ||||
|     /// <param name="modelType"></param> | ||||
|     /// <param name="methodName"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string GetMethodDisplayName(this Type modelType, string methodName) | ||||
|     { | ||||
|         var cacheKey = $"{nameof(GetMethodDisplayName)}-{CultureInfo.CurrentUICulture.Name}-{modelType.FullName}-{modelType.TypeHandle.Value}-{methodName}"; | ||||
|         var displayName = App.CacheService.GetOrCreate(cacheKey, entry => | ||||
|         { | ||||
|             string? dn = null; | ||||
|             // 显示名称为空时通过资源文件查找 methodName 项 | ||||
|             var localizer = modelType.Assembly.IsDynamic ? null : App.CreateLocalizerByType(modelType); | ||||
|             var stringLocalizer = localizer?[methodName]; | ||||
|             if (stringLocalizer is { ResourceNotFound: false }) | ||||
|             { | ||||
|                 dn = stringLocalizer.Value; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 var info = modelType.GetMethodByName(methodName); | ||||
|                 if (info != null) | ||||
|                 { | ||||
|                     dn = FindDisplayAttribute(info); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return dn; | ||||
|         }, 300); | ||||
|  | ||||
|         return displayName ?? methodName; | ||||
|  | ||||
|         string? FindDisplayAttribute(MemberInfo memberInfo) | ||||
|         { | ||||
|             // 回退查找 Display 标签 | ||||
|             var dn = memberInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name | ||||
|                 ?? memberInfo.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName | ||||
|                 ?? memberInfo.GetCustomAttribute<DescriptionAttribute>(true)?.Description; | ||||
|  | ||||
|             return dn; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static bool TryGetProperty(Type modelType, string fieldName, [NotNullWhen(true)] out PropertyInfo? propertyInfo) | ||||
|     { | ||||
|         var cacheKey = $"{nameof(TryGetProperty)}-{modelType.FullName}-{modelType.TypeHandle.Value}-{fieldName}"; | ||||
|         propertyInfo = App.CacheService.GetOrCreate(cacheKey, entry => | ||||
|         { | ||||
|             IEnumerable<PropertyInfo>? props; | ||||
|  | ||||
|             // 支持 MetadataType | ||||
|             var metadataType = modelType.GetCustomAttribute<MetadataTypeAttribute>(false); | ||||
|             if (metadataType != null) | ||||
|             { | ||||
|                 props = modelType.GetRuntimeProperties().AsEnumerable().Concat(metadataType.MetadataClassType.GetRuntimeProperties()); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 props = modelType.GetRuntimeProperties().AsEnumerable(); | ||||
|             } | ||||
|  | ||||
|             var pi = props.FirstOrDefault(p => p.Name == fieldName); | ||||
|  | ||||
|             return pi; | ||||
|         }, 300); | ||||
|         return propertyInfo != null; | ||||
|     } | ||||
| } | ||||
| @@ -1,3 +1,4 @@ | ||||
| 
 | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| @@ -8,7 +9,12 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| using Furion.Extensions; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
| 
 | ||||
| using Microsoft.AspNetCore.Http; | ||||
| 
 | ||||
| using System.Collections.Concurrent; | ||||
| using System.ComponentModel; | ||||
| @@ -17,14 +23,157 @@ using System.Runtime.CompilerServices; | ||||
| using System.Text.Json; | ||||
| using System.Text.RegularExpressions; | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 对象拓展类 | ||||
| /// </summary> | ||||
| [SuppressSniffer] | ||||
| public static class ObjectExtension | ||||
| public static class ObjectExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 将 DateTimeOffset 转换成本地 DateTime | ||||
|     /// </summary> | ||||
|     /// <param name="dateTime"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DateTime ConvertToDateTime(this DateTimeOffset dateTime) | ||||
|     { | ||||
|         if (dateTime.Offset.Equals(TimeSpan.Zero)) | ||||
|             return dateTime.UtcDateTime; | ||||
|         if (dateTime.Offset.Equals(TimeZoneInfo.Local.GetUtcOffset(dateTime.DateTime))) | ||||
|             return dateTime.ToLocalTime().DateTime; | ||||
|         else | ||||
|             return dateTime.DateTime; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 将 DateTimeOffset? 转换成本地 DateTime? | ||||
|     /// </summary> | ||||
|     /// <param name="dateTime"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DateTime? ConvertToDateTime(this DateTimeOffset? dateTime) | ||||
|     { | ||||
|         return dateTime.HasValue ? dateTime.Value.ConvertToDateTime() : null; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 将 DateTime 转换成 DateTimeOffset | ||||
|     /// </summary> | ||||
|     /// <param name="dateTime"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DateTimeOffset ConvertToDateTimeOffset(this DateTime dateTime) | ||||
|     { | ||||
|         return DateTime.SpecifyKind(dateTime, DateTimeKind.Local); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 将 DateTime? 转换成 DateTimeOffset? | ||||
|     /// </summary> | ||||
|     /// <param name="dateTime"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DateTimeOffset? ConvertToDateTimeOffset(this DateTime? dateTime) | ||||
|     { | ||||
|         return dateTime.HasValue ? dateTime.Value.ConvertToDateTimeOffset() : null; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 将时间戳转换为 DateTime | ||||
|     /// </summary> | ||||
|     /// <param name="timestamp"></param> | ||||
|     /// <returns></returns> | ||||
|     internal static DateTime ConvertToDateTime(this long timestamp) | ||||
|     { | ||||
|         var timeStampDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | ||||
|         var digitCount = (int)Math.Floor(Math.Log10(timestamp) + 1); | ||||
| 
 | ||||
|         if (digitCount != 13 && digitCount != 10) | ||||
|         { | ||||
|             throw new ArgumentException("Data is not a valid timestamp format."); | ||||
|         } | ||||
| 
 | ||||
|         return (digitCount == 13 | ||||
|             ? timeStampDateTime.AddMilliseconds(timestamp)  // 13 位时间戳 | ||||
|             : timeStampDateTime.AddSeconds(timestamp)).ToLocalTime();   // 10 位时间戳 | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 将 IFormFile 转换成 byte[] | ||||
|     /// </summary> | ||||
|     /// <param name="formFile"></param> | ||||
|     /// <returns></returns> | ||||
|     public static byte[] ToByteArray(this IFormFile formFile) | ||||
|     { | ||||
|         var fileLength = formFile.Length; | ||||
|         using var stream = formFile.OpenReadStream(); | ||||
|         var bytes = new byte[fileLength]; | ||||
| 
 | ||||
|         stream.Read(bytes, 0, (int)fileLength); | ||||
| 
 | ||||
|         return bytes; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 将流保存到本地磁盘 | ||||
|     /// </summary> | ||||
|     /// <param name="stream"></param> | ||||
|     /// <param name="path"></param> | ||||
|     /// <returns></returns> | ||||
|     public static void CopyToSave(this Stream stream, string path) | ||||
|     { | ||||
|         // 空检查 | ||||
|         if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path)); | ||||
| 
 | ||||
|         using var fileStream = File.Create(path); | ||||
|         stream.CopyTo(fileStream); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 将字节数组保存到本地磁盘 | ||||
|     /// </summary> | ||||
|     /// <param name="bytes"></param> | ||||
|     /// <param name="path"></param> | ||||
|     /// <returns></returns> | ||||
|     public static void CopyToSave(this byte[] bytes, string path) | ||||
|     { | ||||
|         using var stream = new MemoryStream(bytes); | ||||
|         stream.CopyToSave(path); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 将流保存到本地磁盘 | ||||
|     /// </summary> | ||||
|     /// <param name="stream"></param> | ||||
|     /// <param name="path">需包含文件名完整路径</param> | ||||
|     /// <returns></returns> | ||||
|     public static async Task CopyToSaveAsync(this Stream stream, string path) | ||||
|     { | ||||
|         // 空检查 | ||||
|         if (string.IsNullOrWhiteSpace(path)) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(path)); | ||||
|         } | ||||
| 
 | ||||
|         // 文件名判断 | ||||
|         if (string.IsNullOrWhiteSpace(Path.GetFileName(path))) | ||||
|         { | ||||
|             throw new ArgumentException("The parameter of <path> parameter must include the complete file name."); | ||||
|         } | ||||
| 
 | ||||
|         using var fileStream = File.Create(path); | ||||
|         await stream.CopyToAsync(fileStream); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 将字节数组保存到本地磁盘 | ||||
|     /// </summary> | ||||
|     /// <param name="bytes"></param> | ||||
|     /// <param name="path"></param> | ||||
|     /// <returns></returns> | ||||
|     public static async Task CopyToSaveAsync(this byte[] bytes, string path) | ||||
|     { | ||||
|         using var stream = new MemoryStream(bytes); | ||||
|         await stream.CopyToSaveAsync(path); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 判断是否是富基元类型 | ||||
|     /// </summary> | ||||
| @@ -105,32 +254,6 @@ public static class ObjectExtension | ||||
|             || method.ReturnType.ToString().StartsWith(typeof(Task).FullName); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 判断类型是否实现某个泛型 | ||||
|     /// </summary> | ||||
|     /// <param name="type">类型</param> | ||||
|     /// <param name="generic">泛型类型</param> | ||||
|     /// <returns>bool</returns> | ||||
|     public static bool HasImplementedRawGeneric(this Type type, Type generic) | ||||
|     { | ||||
|         // 检查接口类型 | ||||
|         var isTheRawGenericType = type.GetInterfaces().Any(IsTheRawGenericType); | ||||
|         if (isTheRawGenericType) return true; | ||||
| 
 | ||||
|         // 检查类型 | ||||
|         while (type != null && type != typeof(object)) | ||||
|         { | ||||
|             isTheRawGenericType = IsTheRawGenericType(type); | ||||
|             if (isTheRawGenericType) return true; | ||||
|             type = type.BaseType; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
| 
 | ||||
|         // 判断逻辑 | ||||
|         bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 判断是否是匿名类型 | ||||
|     /// </summary> | ||||
| @@ -403,6 +526,12 @@ public static class ObjectExtension | ||||
|                 var _tempStr = !string.IsNullOrWhiteSpace(tempStr) ? tempStr : str; | ||||
|                 tempStr = _tempStr[..^affix.Length]; | ||||
|                 endCleared = true; | ||||
| 
 | ||||
|                 if (string.IsNullOrWhiteSpace(tempStr)) | ||||
|                 { | ||||
|                     tempStr = null; | ||||
|                     endCleared = false; | ||||
|                 } | ||||
|             } | ||||
|             if (startCleared && endCleared) break; | ||||
|         } | ||||
| @@ -0,0 +1,113 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using NewLife.Extension; | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| public static class QueryPageOptionsExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 根据查询条件返回sqlsugar ISugarQueryable | ||||
|     /// </summary> | ||||
|     /// <typeparam name="T"></typeparam> | ||||
|     /// <param name="db"></param> | ||||
|     /// <param name="option"></param> | ||||
|     /// <returns></returns> | ||||
|     public static ISugarQueryable<T> GetQuery<T>(this SqlSugarClient db, QueryPageOptions option) | ||||
|     { | ||||
|         ISugarQueryable<T>? query = db.Queryable<T>(); | ||||
|  | ||||
|         var where = option.ToFilter(); | ||||
|         if (where.HasFilters()) | ||||
|         { | ||||
|             query = query.Where(where.GetFilterLambda<T>());//name asc模式 | ||||
|         } | ||||
|  | ||||
|         foreach (var item in option.SortList) | ||||
|         { | ||||
|             query = query.OrderByIF(!string.IsNullOrEmpty(item), $"{item}");//name asc模式 | ||||
|         } | ||||
|         foreach (var item in option.AdvancedSortList) | ||||
|         { | ||||
|             query = query.OrderByIF(!string.IsNullOrEmpty(item), $"{item}");//name asc模式 | ||||
|         } | ||||
|         query = query.OrderByIF(option.SortOrder != SortOrder.Unset, $"{option.SortName} {option.SortOrder}"); | ||||
|         return query; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 根据查询条件返回IEnumerable | ||||
|     /// </summary> | ||||
|     /// <typeparam name="T"></typeparam> | ||||
|     /// <param name="datas"></param> | ||||
|     /// <param name="option"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IEnumerable<T> GetData<T>(this IEnumerable<T> datas, QueryPageOptions option) | ||||
|     { | ||||
|         var where = option.ToFilter(); | ||||
|         if (where.HasFilters()) | ||||
|         { | ||||
|             datas = datas.Where(where.GetFilterFunc<T>());//name asc模式 | ||||
|         } | ||||
|  | ||||
|         if (option.SortList.Any()) | ||||
|         { | ||||
|             datas = datas.Sort(option.SortList);//name asc模式 | ||||
|         } | ||||
|         if (option.AdvancedSortList.Any()) | ||||
|         { | ||||
|             datas = datas.Sort(option.AdvancedSortList);//name asc模式 | ||||
|         } | ||||
|         if (option.SortOrder != SortOrder.Unset && !option.SortName.IsNullOrWhiteSpace()) | ||||
|         { | ||||
|             datas = datas.Sort(option.SortName, option.SortOrder); | ||||
|         } | ||||
|         if (option.IsPage) | ||||
|         { | ||||
|             datas = datas.Skip((option.PageIndex - 1) * option.PageItems).Take(option.PageItems); | ||||
|         } | ||||
|         else if (option.IsVirtualScroll) | ||||
|         { | ||||
|             datas = datas.Skip((option.StartIndex) * option.PageItems).Take(option.PageItems); | ||||
|         } | ||||
|         return datas; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 根据查询条件返回QueryData | ||||
|     /// </summary> | ||||
|     /// <typeparam name="T"></typeparam> | ||||
|     /// <param name="datas"></param> | ||||
|     /// <param name="option"></param> | ||||
|     /// <returns></returns> | ||||
|     public static QueryData<T> GetQueryData<T>(this IEnumerable<T> datas, QueryPageOptions option) | ||||
|     { | ||||
|         var ret = new QueryData<T>() | ||||
|         { | ||||
|             IsSorted = option.SortOrder != SortOrder.Unset, | ||||
|             IsFiltered = option.Filters.Any(), | ||||
|             IsAdvanceSearch = option.AdvanceSearches.Any(), | ||||
|             IsSearch = option.Searches.Any() || option.CustomerSearches.Any() | ||||
|         }; | ||||
|         var items = datas.GetData(option); | ||||
|         ret.TotalCount = datas.Count(); | ||||
|         ret.Items = items; | ||||
|         return ret; | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,16 +9,31 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| using SqlSugar; | ||||
| 
 | ||||
| using System.Linq.Expressions; | ||||
| using System.Reflection; | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Core; | ||||
| using ThingsGateway.Core; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// Sqlsugar分页拓展类 | ||||
| /// </summary> | ||||
| public static class SqlSugarPageExtension | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| public static class SqlSugarExtensions | ||||
| { | ||||
|     public static async Task<bool> UpdateRangeAsync<T>(this SqlSugarClient db, List<T> updateObjs) where T : class, new() | ||||
|     { | ||||
|         return await db.Updateable(updateObjs).ExecuteCommandAsync() > 0; | ||||
|     } | ||||
| 
 | ||||
|     public static async Task<bool> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new() | ||||
|     { | ||||
|         return await db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression) | ||||
|             .ExecuteCommandAsync() > 0; | ||||
|     } | ||||
| 
 | ||||
|     public static ISugarQueryable<TEntity> ExportIgnoreColumns<TEntity>(this ISugarQueryable<TEntity> queryable) | ||||
|     { | ||||
|         return queryable.IgnoreColumns( | ||||
| @@ -27,7 +43,6 @@ public static class SqlSugarPageExtension | ||||
|            nameof(BaseEntity.CreateTime), | ||||
|            nameof(BaseEntity.CreateUser), | ||||
|            nameof(BaseEntity.CreateUserId), | ||||
|            nameof(BaseEntity.SortCode), | ||||
|            nameof(BaseEntity.ExtJson), | ||||
|            nameof(BaseEntity.IsDelete), | ||||
|            nameof(BaseEntity.UpdateTime), | ||||
| @@ -37,14 +52,6 @@ public static class SqlSugarPageExtension | ||||
|           ); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// SqlSugar分页扩展 | ||||
|     /// </summary> | ||||
|     /// <typeparam name="TEntity"></typeparam> | ||||
|     /// <param name="queryable"></param> | ||||
|     /// <param name="current"></param> | ||||
|     /// <param name="size"></param> | ||||
|     /// <returns></returns> | ||||
|     public static SqlSugarPagedList<TEntity> ToPagedList<TEntity>(this ISugarQueryable<TEntity> queryable, int current, | ||||
|         int size) | ||||
|     { | ||||
| @@ -63,14 +70,6 @@ public static class SqlSugarPageExtension | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// SqlSugar分页扩展 | ||||
|     /// </summary> | ||||
|     /// <typeparam name="TEntity"></typeparam> | ||||
|     /// <param name="queryable"></param> | ||||
|     /// <param name="current"></param> | ||||
|     /// <param name="size"></param> | ||||
|     /// <returns></returns> | ||||
|     public static async Task<SqlSugarPagedList<TEntity>> ToPagedListAsync<TEntity>(this ISugarQueryable<TEntity> queryable, | ||||
|         int current, int size) | ||||
|     { | ||||
| @@ -90,15 +89,50 @@ public static class SqlSugarPageExtension | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// SqlSugar分页扩展 | ||||
|     /// SqlSugar分页扩展,查询出结果后再转换实体类 | ||||
|     /// </summary> | ||||
|     public static SqlSugarPagedList<TResult> ToPagedList<TEntity, TResult>(this ISugarQueryable<TEntity> queryable, | ||||
|         int current, int size) | ||||
|     { | ||||
|         var totalCount = 0; | ||||
|         var records = queryable.ToPageList(current, size, ref totalCount); | ||||
|         var totalPages = (int)Math.Ceiling(totalCount / (double)size); | ||||
|         return new SqlSugarPagedList<TResult> | ||||
|         { | ||||
|             Current = current, | ||||
|             Size = size, | ||||
|             Records = records.Cast<TResult>(), | ||||
|             Total = (int)totalCount, | ||||
|             Pages = totalPages, | ||||
|             HasNextPages = current < totalPages, | ||||
|             HasPrevPages = current - 1 > 0 | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// SqlSugar分页扩展,查询出结果后再转换实体类 | ||||
|     /// </summary> | ||||
|     public static async Task<SqlSugarPagedList<TResult>> ToPagedListAsync<TEntity, TResult>(this ISugarQueryable<TEntity> queryable, | ||||
|         int current, int size) | ||||
|     { | ||||
|         RefAsync<int> totalCount = 0; | ||||
|         var records = await queryable.ToPageListAsync(current, size, totalCount); | ||||
|         var totalPages = (int)Math.Ceiling(totalCount / (double)size); | ||||
|         return new SqlSugarPagedList<TResult> | ||||
|         { | ||||
|             Current = current, | ||||
|             Size = size, | ||||
|             Records = records.Cast<TResult>(), | ||||
|             Total = (int)totalCount, | ||||
|             Pages = totalPages, | ||||
|             HasNextPages = current < totalPages, | ||||
|             HasPrevPages = current - 1 > 0 | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// SqlSugar分页扩展,查询前扩展转换实体类 | ||||
|     /// </summary> | ||||
|     /// <typeparam name="TEntity"></typeparam> | ||||
|     /// <typeparam name="TResult"></typeparam> | ||||
|     /// <param name="queryable"></param> | ||||
|     /// <param name="pageIndex"></param> | ||||
|     /// <param name="pageSize"></param> | ||||
|     /// <param name="expression"></param> | ||||
|     /// <returns></returns> | ||||
|     public static SqlSugarPagedList<TResult> ToPagedList<TEntity, TResult>(this ISugarQueryable<TEntity> queryable, int pageIndex, | ||||
|         int pageSize, Expression<Func<TEntity, TResult>> expression) | ||||
|     { | ||||
| @@ -118,15 +152,9 @@ public static class SqlSugarPageExtension | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// SqlSugar分页扩展 | ||||
|     /// SqlSugar分页扩展,查询前扩展转换实体类 | ||||
|     /// </summary> | ||||
|     /// <typeparam name="TEntity"></typeparam> | ||||
|     /// <typeparam name="TResult"></typeparam> | ||||
|     /// <param name="queryable"></param> | ||||
|     /// <param name="pageIndex"></param> | ||||
|     /// <param name="pageSize"></param> | ||||
|     /// <param name="expression"></param> | ||||
|     /// <returns></returns> | ||||
| 
 | ||||
|     public static async Task<SqlSugarPagedList<TResult>> ToPagedListAsync<TEntity, TResult>( | ||||
|         this ISugarQueryable<TEntity> queryable, int pageIndex, int pageSize, Expression<Func<TEntity, TResult>> expression) | ||||
|     { | ||||
| @@ -0,0 +1,177 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using Microsoft.AspNetCore.Builder; | ||||
| using Microsoft.AspNetCore.Hosting; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Hosting; | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| public static class StartupExtensions | ||||
| { | ||||
|     private static ConcurrentBag<AppStartup> AppStartups = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反射获取所有AppStartup的继承类,执行名称为第一个参数是<see cref="IServiceCollection"/>的方法 | ||||
|     /// </summary> | ||||
|     /// <param name="service"></param> | ||||
|     public static void ConfigureServices(this WebApplicationBuilder service) | ||||
|     { | ||||
|         AddStartups(service); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// ConfigureServices获取的全部实例中,执行名称为第一个参数是<see cref="IApplicationBuilder"/>的方法 | ||||
|     /// </summary> | ||||
|     public static void UseServices(this IApplicationBuilder builder) | ||||
|     { | ||||
|         UseStartups(AppStartups, builder); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 添加 Startup 自动扫描 | ||||
|     /// </summary> | ||||
|     internal static void AddStartups(this WebApplicationBuilder builder) | ||||
|     { | ||||
|         App.Configuration = builder.Configuration; | ||||
|         if (builder.Environment is IWebHostEnvironment webHostEnvironment) | ||||
|             App.WebRootPath = webHostEnvironment.WebRootPath; | ||||
|         App.ContentRootPath = builder.Environment.ContentRootPath; | ||||
|         App.IsDevelopment = builder.Environment.IsDevelopment(); | ||||
|  | ||||
|         // 扫描所有继承 AppStartup 的类 | ||||
|         var startups = App.EffectiveTypes | ||||
|             .Where(u => typeof(AppStartup).IsAssignableFrom(u) && u.IsClass && !u.IsAbstract && !u.IsGenericType) | ||||
|             .OrderByDescending(u => GetStartupOrder(u)); | ||||
|  | ||||
|         // 注册自定义 startup | ||||
|         foreach (var type in startups) | ||||
|         { | ||||
|             var startup = Activator.CreateInstance(type) as AppStartup; | ||||
|             AppStartups.Add(startup!); | ||||
|  | ||||
|             // 获取所有符合依赖注入格式的方法,如返回值void,且第一个参数是 IServiceCollection 类型 | ||||
|             var serviceMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance) | ||||
|                 .Where(u => u.ReturnType == typeof(void) | ||||
|                     && u.GetParameters().Length > 0 | ||||
|                     && u.GetParameters().First().ParameterType == typeof(IServiceCollection)); | ||||
|  | ||||
|             if (!serviceMethods.Any()) continue; | ||||
|  | ||||
|             // 自动安装属性调用 | ||||
|             foreach (var method in serviceMethods) | ||||
|             { | ||||
|                 method.Invoke(startup, new[] { builder.Services }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 批量将自定义 AppStartup 添加到 Startup.cs 的 Configure 中 | ||||
|     /// </summary> | ||||
|     /// <param name="startups"></param> | ||||
|     /// <param name="app"></param> | ||||
|     private static void UseStartups(IEnumerable<AppStartup> startups, IApplicationBuilder app) | ||||
|     { | ||||
|         App.RootServices = app.ApplicationServices; | ||||
|         App.CacheService = app.ApplicationServices.GetRequiredService<ICacheService>(); | ||||
|  | ||||
|         // 遍历所有 | ||||
|         foreach (var startup in startups) | ||||
|         { | ||||
|             var type = startup.GetType(); | ||||
|  | ||||
|             // 获取所有符合依赖注入格式的方法,如返回值 void,且第一个参数是 IApplicationBuilder 类型 | ||||
|             var configureMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance) | ||||
|                 .Where(u => u.ReturnType == typeof(void) | ||||
|                     && u.GetParameters().Length > 0 | ||||
|                     && u.GetParameters().First().ParameterType == typeof(IApplicationBuilder)); | ||||
|  | ||||
|             if (!configureMethods.Any()) continue; | ||||
|  | ||||
|             // 自动安装属性调用 | ||||
|             foreach (var method in configureMethods) | ||||
|             { | ||||
|                 method.Invoke(startup, ResolveMethodParameterInstances(app, method)); | ||||
|             } | ||||
|         } | ||||
|         AppStartups.Clear(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取 Startup 排序 | ||||
|     /// </summary> | ||||
|     /// <param name="type">排序类型</param> | ||||
|     /// <returns>int</returns> | ||||
|     private static int GetStartupOrder(Type type) | ||||
|     { | ||||
|         return !type.IsDefined(typeof(AppStartupAttribute), true) ? 0 : type.GetCustomAttribute<AppStartupAttribute>(true)!.Order; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 解析方法参数实例 | ||||
|     /// </summary> | ||||
|     /// <param name="app"></param> | ||||
|     /// <param name="method"></param> | ||||
|     /// <returns></returns> | ||||
|     private static object[] ResolveMethodParameterInstances(IApplicationBuilder app, MethodInfo method) | ||||
|     { | ||||
|         // 获取方法所有参数 | ||||
|         var parameters = method.GetParameters(); | ||||
|         var parameterInstances = new object[parameters.Length]; | ||||
|         parameterInstances[0] = app; | ||||
|  | ||||
|         // 解析服务 | ||||
|         for (var i = 1; i < parameters.Length; i++) | ||||
|         { | ||||
|             var parameter = parameters[i]; | ||||
|             parameterInstances[i] = app.ApplicationServices.GetRequiredService(parameter.ParameterType); | ||||
|         } | ||||
|  | ||||
|         return parameterInstances; | ||||
|     } | ||||
| } | ||||
|  | ||||
| public abstract class AppStartup | ||||
| { | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// 注册服务启动配置 | ||||
| /// </summary> | ||||
| [AttributeUsage(AttributeTargets.Class)] | ||||
| public class AppStartupAttribute : Attribute | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="order"></param> | ||||
|     public AppStartupAttribute(int order) | ||||
|     { | ||||
|         Order = order; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 排序 | ||||
|     /// </summary> | ||||
|     public int Order { get; set; } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -8,12 +9,15 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Core.Extension; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// TextWriter扩展 | ||||
| /// </summary> | ||||
| public static class TextWriterExtension | ||||
| public static class TextWriterExtensions | ||||
| { | ||||
|     private const string DefaultBackgroundColor = "\x1B[49m"; | ||||
|     private const string DefaultForegroundColor = "\x1B[39m\x1B[22m"; | ||||
							
								
								
									
										3
									
								
								src/ThingsGateway.Admin.Application/FodyWeavers.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | ||||
| <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> | ||||
|   <Rougamo /> | ||||
| </Weavers> | ||||
| @@ -0,0 +1,133 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| #if !NET5_0 | ||||
|  | ||||
| using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| namespace ThingsGateway.JsonSerialization; | ||||
|  | ||||
| /// <summary> | ||||
| /// DateOnly 类型序列化 | ||||
| /// </summary> | ||||
| public class NewtonsoftJsonDateOnlyJsonConverter : JsonConverter<DateOnly> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public NewtonsoftJsonDateOnlyJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public NewtonsoftJsonDateOnlyJsonConverter(string format = "yyyy-MM-dd") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 日期格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="objectType"></param> | ||||
|     /// <param name="existingValue"></param> | ||||
|     /// <param name="hasExistingValue"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <returns></returns> | ||||
|     public override DateOnly ReadJson(JsonReader reader, Type objectType, DateOnly existingValue, bool hasExistingValue, JsonSerializer serializer) | ||||
|     { | ||||
|         var value = JValue.ReadFrom(reader).Value<string>(); | ||||
|         return DateOnly.Parse(value); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     public override void WriteJson(JsonWriter writer, DateOnly value, JsonSerializer serializer) | ||||
|     { | ||||
|         serializer.Serialize(writer, value.ToString(Format)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// DateOnly? 类型序列化 | ||||
| /// </summary> | ||||
| public class NewtonsoftJsonNullableDateOnlyJsonConverter : JsonConverter<DateOnly?> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public NewtonsoftJsonNullableDateOnlyJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public NewtonsoftJsonNullableDateOnlyJsonConverter(string format = "yyyy-MM-dd") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 日期格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="objectType"></param> | ||||
|     /// <param name="existingValue"></param> | ||||
|     /// <param name="hasExistingValue"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <returns></returns> | ||||
|     public override DateOnly? ReadJson(JsonReader reader, Type objectType, DateOnly? existingValue, bool hasExistingValue, JsonSerializer serializer) | ||||
|     { | ||||
|         var value = JValue.ReadFrom(reader).Value<string>(); | ||||
|         return !string.IsNullOrWhiteSpace(value) ? DateOnly.Parse(value) : null; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     public override void WriteJson(JsonWriter writer, DateOnly? value, JsonSerializer serializer) | ||||
|     { | ||||
|         if (value == null) writer.WriteNull(); | ||||
|         else serializer.Serialize(writer, value.Value.ToString(Format)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #endif | ||||
| @@ -0,0 +1,130 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using Newtonsoft.Json; | ||||
|  | ||||
| namespace ThingsGateway.JsonSerialization; | ||||
|  | ||||
| /// <summary> | ||||
| /// DateTime 类型序列化 | ||||
| /// </summary> | ||||
| public class NewtonsoftJsonDateTimeJsonConverter : JsonConverter<DateTime> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 默认构造函数 | ||||
|     /// </summary> | ||||
|     public NewtonsoftJsonDateTimeJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public NewtonsoftJsonDateTimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 时间格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="objectType"></param> | ||||
|     /// <param name="existingValue"></param> | ||||
|     /// <param name="hasExistingValue"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <returns></returns> | ||||
|     /// <exception cref="NotImplementedException"></exception> | ||||
|     public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer) | ||||
|     { | ||||
|         return Penetrates.ConvertToDateTime(ref reader); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <exception cref="NotImplementedException"></exception> | ||||
|     public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer) | ||||
|     { | ||||
|         serializer.Serialize(writer, value.ToString(Format)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// DateTime 类型序列化 | ||||
| /// </summary> | ||||
| public class NewtonsoftNullableJsonDateTimeJsonConverter : JsonConverter<DateTime?> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 默认构造函数 | ||||
|     /// </summary> | ||||
|     public NewtonsoftNullableJsonDateTimeJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public NewtonsoftNullableJsonDateTimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 时间格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="objectType"></param> | ||||
|     /// <param name="existingValue"></param> | ||||
|     /// <param name="hasExistingValue"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <returns></returns> | ||||
|     /// <exception cref="NotImplementedException"></exception> | ||||
|     public override DateTime? ReadJson(JsonReader reader, Type objectType, DateTime? existingValue, bool hasExistingValue, JsonSerializer serializer) | ||||
|     { | ||||
|         return Penetrates.ConvertToDateTime(ref reader); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <exception cref="NotImplementedException"></exception> | ||||
|     public override void WriteJson(JsonWriter writer, DateTime? value, JsonSerializer serializer) | ||||
|     { | ||||
|         if (value == null) writer.WriteNull(); | ||||
|         else serializer.Serialize(writer, value.Value.ToString(Format)); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,171 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using Newtonsoft.Json; | ||||
|  | ||||
| using ThingsGateway.Core.Extension; | ||||
|  | ||||
| namespace ThingsGateway.JsonSerialization; | ||||
|  | ||||
| /// <summary> | ||||
| /// DateTimeOffset 类型序列化 | ||||
| /// </summary> | ||||
| public class NewtonsoftJsonDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public NewtonsoftJsonDateTimeOffsetJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public NewtonsoftJsonDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     /// <param name="outputToLocalDateTime"></param> | ||||
|     public NewtonsoftJsonDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss", bool outputToLocalDateTime = false) | ||||
|         : this(format) | ||||
|     { | ||||
|         Localized = outputToLocalDateTime; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 时间格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否输出为为当地时间 | ||||
|     /// </summary> | ||||
|     public bool Localized { get; private set; } = false; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="objectType"></param> | ||||
|     /// <param name="existingValue"></param> | ||||
|     /// <param name="hasExistingValue"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <returns></returns> | ||||
|     /// <exception cref="NotImplementedException"></exception> | ||||
|     public override DateTimeOffset ReadJson(JsonReader reader, Type objectType, DateTimeOffset existingValue, bool hasExistingValue, JsonSerializer serializer) | ||||
|     { | ||||
|         return DateTime.SpecifyKind(Penetrates.ConvertToDateTime(ref reader), Localized ? DateTimeKind.Local : DateTimeKind.Utc); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <exception cref="NotImplementedException"></exception> | ||||
|     public override void WriteJson(JsonWriter writer, DateTimeOffset value, JsonSerializer serializer) | ||||
|     { | ||||
|         // 判断是否序列化成当地时间 | ||||
|         var formatDateTime = Localized ? value.ConvertToDateTime() : value; | ||||
|         serializer.Serialize(writer, formatDateTime.ToString(Format)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// DateTimeOffset 类型序列化 | ||||
| /// </summary> | ||||
| public class NewtonsoftJsonNullableDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset?> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public NewtonsoftJsonNullableDateTimeOffsetJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public NewtonsoftJsonNullableDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     /// <param name="outputToLocalDateTime"></param> | ||||
|     public NewtonsoftJsonNullableDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss", bool outputToLocalDateTime = false) | ||||
|         : this(format) | ||||
|     { | ||||
|         Localized = outputToLocalDateTime; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 时间格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否输出为为当地时间 | ||||
|     /// </summary> | ||||
|     public bool Localized { get; private set; } = false; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="objectType"></param> | ||||
|     /// <param name="existingValue"></param> | ||||
|     /// <param name="hasExistingValue"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <returns></returns> | ||||
|     /// <exception cref="NotImplementedException"></exception> | ||||
|     public override DateTimeOffset? ReadJson(JsonReader reader, Type objectType, DateTimeOffset? existingValue, bool hasExistingValue, JsonSerializer serializer) | ||||
|     { | ||||
|         return DateTime.SpecifyKind(Penetrates.ConvertToDateTime(ref reader), Localized ? DateTimeKind.Local : DateTimeKind.Utc); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <exception cref="NotImplementedException"></exception> | ||||
|     public override void WriteJson(JsonWriter writer, DateTimeOffset? value, JsonSerializer serializer) | ||||
|     { | ||||
|         if (value == null) writer.WriteNull(); | ||||
|         else | ||||
|         { | ||||
|             // 判断是否序列化成当地时间 | ||||
|             var formatDateTime = Localized ? value.ConvertToDateTime() : value; | ||||
|             serializer.Serialize(writer, formatDateTime!.Value.ToString(Format)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,144 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| namespace ThingsGateway.JsonSerialization; | ||||
|  | ||||
| /// <summary> | ||||
| /// 解决 long 精度问题 | ||||
| /// </summary> | ||||
| public class NewtonsoftJsonLongToStringJsonConverter : JsonConverter<long> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public NewtonsoftJsonLongToStringJsonConverter() | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="overMaxLengthOf17"></param> | ||||
|     public NewtonsoftJsonLongToStringJsonConverter(bool overMaxLengthOf17 = false) | ||||
|     { | ||||
|         OverMaxLengthOf17 = overMaxLengthOf17; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否超过最大长度 17 再处理 | ||||
|     /// </summary> | ||||
|     public bool OverMaxLengthOf17 { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="objectType"></param> | ||||
|     /// <param name="existingValue"></param> | ||||
|     /// <param name="hasExistingValue"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <returns></returns> | ||||
|     public override long ReadJson(JsonReader reader, Type objectType, long existingValue, bool hasExistingValue, JsonSerializer serializer) | ||||
|     { | ||||
|         var jt = JValue.ReadFrom(reader); | ||||
|  | ||||
|         return jt.Type == JTokenType.Null   // 处理 public long? Property { get; set;} = 0; 情况,也就是类型是 long? 但是也给了默认值 | ||||
|             ? existingValue | ||||
|             : jt.Value<long>(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     public override void WriteJson(JsonWriter writer, long value, JsonSerializer serializer) | ||||
|     { | ||||
|         if (OverMaxLengthOf17) | ||||
|         { | ||||
|             if (value.ToString().Length <= 17) writer.WriteValue(value); | ||||
|             else writer.WriteValue(value.ToString()); | ||||
|         } | ||||
|         else writer.WriteValue(value.ToString()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// 解决 long? 精度问题 | ||||
| /// </summary> | ||||
| public class NewtonsoftJsonNullableLongToStringJsonConverter : JsonConverter<long?> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public NewtonsoftJsonNullableLongToStringJsonConverter() | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="overMaxLengthOf17"></param> | ||||
|     public NewtonsoftJsonNullableLongToStringJsonConverter(bool overMaxLengthOf17 = false) | ||||
|     { | ||||
|         OverMaxLengthOf17 = overMaxLengthOf17; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否超过最大长度 17 再处理 | ||||
|     /// </summary> | ||||
|     public bool OverMaxLengthOf17 { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="objectType"></param> | ||||
|     /// <param name="existingValue"></param> | ||||
|     /// <param name="hasExistingValue"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <returns></returns> | ||||
|     public override long? ReadJson(JsonReader reader, Type objectType, long? existingValue, bool hasExistingValue, JsonSerializer serializer) | ||||
|     { | ||||
|         var jt = JValue.ReadFrom(reader); | ||||
|         return jt.Value<long?>(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     public override void WriteJson(JsonWriter writer, long? value, JsonSerializer serializer) | ||||
|     { | ||||
|         if (value == null) writer.WriteNull(); | ||||
|         else | ||||
|         { | ||||
|             var newValue = value.Value; | ||||
|             if (OverMaxLengthOf17) | ||||
|             { | ||||
|                 if (newValue.ToString().Length <= 17) writer.WriteValue(newValue); | ||||
|                 else writer.WriteValue(newValue.ToString()); | ||||
|             } | ||||
|             else writer.WriteValue(newValue.ToString()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,133 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| #if !NET5_0 | ||||
|  | ||||
| using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| namespace ThingsGateway.JsonSerialization; | ||||
|  | ||||
| /// <summary> | ||||
| /// TimeOnly 类型序列化 | ||||
| /// </summary> | ||||
| public class NewtonsoftJsonTimeOnlyJsonConverter : JsonConverter<TimeOnly> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public NewtonsoftJsonTimeOnlyJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public NewtonsoftJsonTimeOnlyJsonConverter(string format = "HH:mm:ss") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 时间格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="objectType"></param> | ||||
|     /// <param name="existingValue"></param> | ||||
|     /// <param name="hasExistingValue"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <returns></returns> | ||||
|     public override TimeOnly ReadJson(JsonReader reader, Type objectType, TimeOnly existingValue, bool hasExistingValue, JsonSerializer serializer) | ||||
|     { | ||||
|         var value = JValue.ReadFrom(reader).Value<string>(); | ||||
|         return TimeOnly.Parse(value!); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     public override void WriteJson(JsonWriter writer, TimeOnly value, JsonSerializer serializer) | ||||
|     { | ||||
|         serializer.Serialize(writer, value.ToString(Format)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// TimeOnly? 类型序列化 | ||||
| /// </summary> | ||||
| public class NewtonsoftJsonNullableTimeOnlyJsonConverter : JsonConverter<TimeOnly?> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public NewtonsoftJsonNullableTimeOnlyJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public NewtonsoftJsonNullableTimeOnlyJsonConverter(string format = "HH:mm:ss") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 时间格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="objectType"></param> | ||||
|     /// <param name="existingValue"></param> | ||||
|     /// <param name="hasExistingValue"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     /// <returns></returns> | ||||
|     public override TimeOnly? ReadJson(JsonReader reader, Type objectType, TimeOnly? existingValue, bool hasExistingValue, JsonSerializer serializer) | ||||
|     { | ||||
|         var value = JValue.ReadFrom(reader).Value<string>(); | ||||
|         return !string.IsNullOrWhiteSpace(value) ? TimeOnly.Parse(value) : null; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="serializer"></param> | ||||
|     public override void WriteJson(JsonWriter writer, TimeOnly? value, JsonSerializer serializer) | ||||
|     { | ||||
|         if (value == null) writer.WriteNull(); | ||||
|         else serializer.Serialize(writer, value.Value.ToString(Format)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #endif | ||||
| @@ -0,0 +1,123 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using System.Text.Json; | ||||
| using System.Text.Json.Serialization; | ||||
|  | ||||
| namespace ThingsGateway.JsonSerialization; | ||||
|  | ||||
| /// <summary> | ||||
| /// DateOnly 类型序列化 | ||||
| /// </summary> | ||||
| public class SystemTextJsonDateOnlyJsonConverter : JsonConverter<DateOnly> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public SystemTextJsonDateOnlyJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public SystemTextJsonDateOnlyJsonConverter(string format = "yyyy-MM-dd") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 日期格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="typeToConvert"></param> | ||||
|     /// <param name="options"></param> | ||||
|     /// <returns></returns> | ||||
|     public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|     { | ||||
|         return DateOnly.Parse(reader.GetString()!); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="options"></param> | ||||
|     public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options) | ||||
|     { | ||||
|         writer.WriteStringValue(value.ToString(Format)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// DateOnly? 类型序列化 | ||||
| /// </summary> | ||||
| public class SystemTextJsonNullableDateOnlyJsonConverter : JsonConverter<DateOnly?> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public SystemTextJsonNullableDateOnlyJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public SystemTextJsonNullableDateOnlyJsonConverter(string format = "yyyy-MM-dd") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 日期格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="typeToConvert"></param> | ||||
|     /// <param name="options"></param> | ||||
|     /// <returns></returns> | ||||
|     public override DateOnly? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|     { | ||||
|         return DateOnly.TryParse(reader.GetString(), out var date) ? date : null; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="options"></param> | ||||
|     public override void Write(Utf8JsonWriter writer, DateOnly? value, JsonSerializerOptions options) | ||||
|     { | ||||
|         if (value == null) writer.WriteNullValue(); | ||||
|         else writer.WriteStringValue(value.Value.ToString(Format)); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,123 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using System.Text.Json; | ||||
| using System.Text.Json.Serialization; | ||||
|  | ||||
| namespace ThingsGateway.JsonSerialization; | ||||
|  | ||||
| /// <summary> | ||||
| /// DateTime 类型序列化 | ||||
| /// </summary> | ||||
| public class SystemTextJsonDateTimeJsonConverter : JsonConverter<DateTime> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 默认构造函数 | ||||
|     /// </summary> | ||||
|     public SystemTextJsonDateTimeJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public SystemTextJsonDateTimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 时间格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="typeToConvert"></param> | ||||
|     /// <param name="options"></param> | ||||
|     /// <returns></returns> | ||||
|     public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|     { | ||||
|         return Penetrates.ConvertToDateTime(ref reader); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="options"></param> | ||||
|     public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) | ||||
|     { | ||||
|         writer.WriteStringValue(value.ToString(Format)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// DateTime? 类型序列化 | ||||
| /// </summary> | ||||
| public class SystemTextJsonNullableDateTimeJsonConverter : JsonConverter<DateTime?> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 默认构造函数 | ||||
|     /// </summary> | ||||
|     public SystemTextJsonNullableDateTimeJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public SystemTextJsonNullableDateTimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 时间格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="typeToConvert"></param> | ||||
|     /// <param name="options"></param> | ||||
|     /// <returns></returns> | ||||
|     public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|     { | ||||
|         return Penetrates.ConvertToDateTime(ref reader); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="options"></param> | ||||
|     public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) | ||||
|     { | ||||
|         if (value == null) writer.WriteNullValue(); | ||||
|         else writer.WriteStringValue(value.Value.ToString(Format)); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,164 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using System.Text.Json; | ||||
| using System.Text.Json.Serialization; | ||||
|  | ||||
| using ThingsGateway.Core.Extension; | ||||
|  | ||||
| namespace ThingsGateway.JsonSerialization; | ||||
|  | ||||
| /// <summary> | ||||
| /// DateTimeOffset 类型序列化 | ||||
| /// </summary> | ||||
| public class SystemTextJsonDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public SystemTextJsonDateTimeOffsetJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public SystemTextJsonDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     /// <param name="outputToLocalDateTime"></param> | ||||
|     public SystemTextJsonDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss", bool outputToLocalDateTime = false) | ||||
|         : this(format) | ||||
|     { | ||||
|         Localized = outputToLocalDateTime; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 时间格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否输出为为当地时间 | ||||
|     /// </summary> | ||||
|     public bool Localized { get; private set; } = false; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="typeToConvert"></param> | ||||
|     /// <param name="options"></param> | ||||
|     /// <returns></returns> | ||||
|     public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|     { | ||||
|         return DateTime.SpecifyKind(Penetrates.ConvertToDateTime(ref reader), Localized ? DateTimeKind.Local : DateTimeKind.Utc); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="options"></param> | ||||
|     public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) | ||||
|     { | ||||
|         // 判断是否序列化成当地时间 | ||||
|         var formatDateTime = Localized ? value.ConvertToDateTime() : value; | ||||
|         writer.WriteStringValue(formatDateTime.ToString(Format)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// DateTimeOffset? 类型序列化 | ||||
| /// </summary> | ||||
| public class SystemTextJsonNullableDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset?> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public SystemTextJsonNullableDateTimeOffsetJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public SystemTextJsonNullableDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     /// <param name="outputToLocalDateTime"></param> | ||||
|     public SystemTextJsonNullableDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss", bool outputToLocalDateTime = false) | ||||
|         : this(format) | ||||
|     { | ||||
|         Localized = outputToLocalDateTime; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 时间格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否输出为为当地时间 | ||||
|     /// </summary> | ||||
|     public bool Localized { get; private set; } = false; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="typeToConvert"></param> | ||||
|     /// <param name="options"></param> | ||||
|     /// <returns></returns> | ||||
|     public override DateTimeOffset? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|     { | ||||
|         return DateTime.SpecifyKind(Penetrates.ConvertToDateTime(ref reader), Localized ? DateTimeKind.Local : DateTimeKind.Utc); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="options"></param> | ||||
|     public override void Write(Utf8JsonWriter writer, DateTimeOffset? value, JsonSerializerOptions options) | ||||
|     { | ||||
|         if (value == null) writer.WriteNullValue(); | ||||
|         else | ||||
|         { | ||||
|             // 判断是否序列化成当地时间 | ||||
|             var formatDateTime = Localized ? value.ConvertToDateTime() : value; | ||||
|             writer.WriteStringValue(formatDateTime!.Value.ToString(Format)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,139 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using System.Text.Json; | ||||
| using System.Text.Json.Serialization; | ||||
|  | ||||
| namespace ThingsGateway.JsonSerialization; | ||||
|  | ||||
| /// <summary> | ||||
| /// 解决 long 精度问题 | ||||
| /// </summary> | ||||
| public class SystemTextJsonLongToStringJsonConverter : JsonConverter<long> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public SystemTextJsonLongToStringJsonConverter() | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="overMaxLengthOf17"></param> | ||||
|     public SystemTextJsonLongToStringJsonConverter(bool overMaxLengthOf17 = false) | ||||
|     { | ||||
|         OverMaxLengthOf17 = overMaxLengthOf17; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否超过最大长度 17 再处理 | ||||
|     /// </summary> | ||||
|     public bool OverMaxLengthOf17 { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="typeToConvert"></param> | ||||
|     /// <param name="options"></param> | ||||
|     /// <returns></returns> | ||||
|     public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|     { | ||||
|         return reader.TokenType == JsonTokenType.String | ||||
|                 ? long.Parse(reader.GetString()!) | ||||
|                 : reader.GetInt64(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="options"></param> | ||||
|     public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options) | ||||
|     { | ||||
|         if (OverMaxLengthOf17) | ||||
|         { | ||||
|             if (value.ToString().Length <= 17) writer.WriteNumberValue(value); | ||||
|             else writer.WriteStringValue(value.ToString()); | ||||
|         } | ||||
|         else writer.WriteStringValue(value.ToString()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// 解决 long? 精度问题 | ||||
| /// </summary> | ||||
| public class SystemTextJsonNullableLongToStringJsonConverter : JsonConverter<long?> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public SystemTextJsonNullableLongToStringJsonConverter() | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="overMaxLengthOf17"></param> | ||||
|     public SystemTextJsonNullableLongToStringJsonConverter(bool overMaxLengthOf17 = false) | ||||
|     { | ||||
|         OverMaxLengthOf17 = overMaxLengthOf17; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否超过最大长度 17 再处理 | ||||
|     /// </summary> | ||||
|     public bool OverMaxLengthOf17 { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="typeToConvert"></param> | ||||
|     /// <param name="options"></param> | ||||
|     /// <returns></returns> | ||||
|     public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|     { | ||||
|         return reader.TokenType == JsonTokenType.String | ||||
|                 ? long.Parse(reader.GetString()!) | ||||
|                 : reader.GetInt64(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="options"></param> | ||||
|     public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options) | ||||
|     { | ||||
|         if (value == null) writer.WriteNullValue(); | ||||
|         else | ||||
|         { | ||||
|             var newValue = value.Value; | ||||
|             if (OverMaxLengthOf17) | ||||
|             { | ||||
|                 if (newValue.ToString().Length <= 17) writer.WriteNumberValue(newValue); | ||||
|                 else writer.WriteStringValue(newValue.ToString()); | ||||
|             } | ||||
|             else writer.WriteStringValue(newValue.ToString()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,127 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| #if !NET5_0 | ||||
|  | ||||
| using System.Text.Json; | ||||
| using System.Text.Json.Serialization; | ||||
|  | ||||
| namespace ThingsGateway.JsonSerialization; | ||||
|  | ||||
| /// <summary> | ||||
| /// TimeOnly 类型序列化 | ||||
| /// </summary> | ||||
| public class SystemTextJsonTimeOnlyJsonConverter : JsonConverter<TimeOnly> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     public SystemTextJsonTimeOnlyJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public SystemTextJsonTimeOnlyJsonConverter(string format = "HH:mm:ss") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 时间格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="typeToConvert"></param> | ||||
|     /// <param name="options"></param> | ||||
|     /// <returns></returns> | ||||
|     public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|     { | ||||
|         return TimeOnly.Parse(reader.GetString()!); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="options"></param> | ||||
|     public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options) | ||||
|     { | ||||
|         writer.WriteStringValue(value.ToString(Format)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// TimeOnly? 类型序列化 | ||||
| /// </summary> | ||||
| public class SystemTextJsonNullableTimeOnlyJsonConverter : JsonConverter<TimeOnly?> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 默认构造函数 | ||||
|     /// </summary> | ||||
|     public SystemTextJsonNullableTimeOnlyJsonConverter() | ||||
|         : this(default) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="format"></param> | ||||
|     public SystemTextJsonNullableTimeOnlyJsonConverter(string format = "HH:mm:ss") | ||||
|     { | ||||
|         Format = format; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 时间格式化格式 | ||||
|     /// </summary> | ||||
|     public string Format { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="reader"></param> | ||||
|     /// <param name="typeToConvert"></param> | ||||
|     /// <param name="options"></param> | ||||
|     /// <returns></returns> | ||||
|     public override TimeOnly? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|     { | ||||
|         return TimeOnly.TryParse(reader.GetString(), out var time) ? time : null; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     /// <param name="writer"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <param name="options"></param> | ||||
|     public override void Write(Utf8JsonWriter writer, TimeOnly? value, JsonSerializerOptions options) | ||||
|     { | ||||
|         if (value == null) writer.WriteNullValue(); | ||||
|         else writer.WriteStringValue(value.Value.ToString(Format)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #endif | ||||
| @@ -0,0 +1,58 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
|  | ||||
| using ThingsGateway.JsonSerialization; | ||||
|  | ||||
| namespace Microsoft.Extensions.DependencyInjection; | ||||
|  | ||||
| /// <summary> | ||||
| /// Json 序列化服务拓展类 | ||||
| /// </summary> | ||||
| public static class JsonSerializationServiceCollectionExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 配置 Json 序列化提供器 | ||||
|     /// </summary> | ||||
|     /// <typeparam name="TJsonSerializerProvider"></typeparam> | ||||
|     /// <param name="services"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IServiceCollection AddJsonSerialization<TJsonSerializerProvider>(this IServiceCollection services) | ||||
|         where TJsonSerializerProvider : class, IJsonSerializerProvider | ||||
|     { | ||||
|         services.AddSingleton<IJsonSerializerProvider, TJsonSerializerProvider>(); | ||||
|         return services; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 配置 JsonOptions 序列化选项 | ||||
|     /// <para>主要给非 Web 环境使用</para> | ||||
|     /// </summary> | ||||
|     /// <param name="services"></param> | ||||
|     /// <param name="configure"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IServiceCollection AddJsonOptions(this IServiceCollection services, Action<JsonOptions> configure) | ||||
|     { | ||||
|         // 手动添加配置 | ||||
|         services.Configure<JsonOptions>(options => | ||||
|         { | ||||
|             configure?.Invoke(options); | ||||
|         }); | ||||
|  | ||||
|         return services; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,85 @@ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||
|  | ||||
| using ThingsGateway.JsonSerialization; | ||||
|  | ||||
| namespace Newtonsoft.Json; | ||||
|  | ||||
| /// <summary> | ||||
| /// Newtonsoft.Json 拓展 | ||||
| /// </summary> | ||||
| public static class NewtonsoftJsonExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 添加 DateTime/DateTime?/DateTimeOffset/DateTimeOffset? 类型序列化处理 | ||||
|     /// </summary> | ||||
|     /// <param name="converters"></param> | ||||
|     /// <param name="outputFormat"></param> | ||||
|     /// <param name="localized">自动转换 DateTimeOffset 为当地时间</param> | ||||
|     /// <returns></returns> | ||||
|     public static IList<JsonConverter> AddDateTimeTypeConverters(this IList<JsonConverter> converters, string outputFormat = "yyyy-MM-dd HH:mm:ss", bool localized = false) | ||||
|     { | ||||
|         converters.Add(new NewtonsoftJsonDateTimeJsonConverter(outputFormat)); | ||||
|         converters.Add(new NewtonsoftNullableJsonDateTimeJsonConverter(outputFormat)); | ||||
|  | ||||
|         converters.Add(new NewtonsoftJsonDateTimeOffsetJsonConverter(outputFormat, localized)); | ||||
|         converters.Add(new NewtonsoftJsonNullableDateTimeOffsetJsonConverter(outputFormat, localized)); | ||||
|  | ||||
|         return converters; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 添加 long/long? 类型序列化处理 | ||||
|     /// </summary> | ||||
|     /// <param name="converters"></param> | ||||
|     /// <param name="overMaxLengthOf17">是否超过最大长度 17 再处理</param> | ||||
|     /// <remarks></remarks> | ||||
|     public static IList<JsonConverter> AddLongTypeConverters(this IList<JsonConverter> converters, bool overMaxLengthOf17 = false) | ||||
|     { | ||||
|         converters.Add(new NewtonsoftJsonLongToStringJsonConverter(overMaxLengthOf17)); | ||||
|         converters.Add(new NewtonsoftJsonNullableLongToStringJsonConverter(overMaxLengthOf17)); | ||||
|  | ||||
|         return converters; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 添加 DateOnly/DateOnly? 类型序列化处理 | ||||
|     /// </summary> | ||||
|     /// <param name="converters"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IList<JsonConverter> AddDateOnlyConverters(this IList<JsonConverter> converters) | ||||
|     { | ||||
| #if !NET5_0 | ||||
|         converters.Add(new NewtonsoftJsonDateOnlyJsonConverter()); | ||||
|         converters.Add(new NewtonsoftJsonNullableDateOnlyJsonConverter()); | ||||
| #endif | ||||
|         return converters; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 添加 TimeOnly/TimeOnly? 类型序列化处理 | ||||
|     /// </summary> | ||||
|     /// <param name="converters"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IList<JsonConverter> AddTimeOnlyConverters(this IList<JsonConverter> converters) | ||||
|     { | ||||
| #if !NET5_0 | ||||
|         converters.Add(new NewtonsoftJsonTimeOnlyJsonConverter()); | ||||
|         converters.Add(new NewtonsoftJsonNullableTimeOnlyJsonConverter()); | ||||
| #endif | ||||
|         return converters; | ||||
|     } | ||||
| } | ||||