Compare commits

...

192 Commits

Author SHA1 Message Date
Kimdiego2098
a533286658 更新版本号 2023-11-20 23:05:12 +08:00
Kimdiego2098
e59f91cd82 去除treeview 2023-11-20 23:04:54 +08:00
Kimdiego2098
5f8b85d8a4 去除treeview显示 2023-11-20 22:53:34 +08:00
Kimdiego2098
47c7b88436 更新版本号 2023-11-20 22:47:18 +08:00
Kimdiego2098
90006782f2 mqttserver/client插件支持websocket通道,直接对接前端 2023-11-20 22:42:08 +08:00
Kimdiego2098
c3d49cbe70 mqttserver/client插件支持websocket通道,直接对接前端 2023-11-20 22:33:26 +08:00
Kimdiego2098
112323a360 modbusTcpServer最大连接数设为3w 2023-11-20 19:46:07 +08:00
Kimdiego2098
9d08c90fda 更新依赖 2023-11-20 17:35:42 +08:00
Kimdiego2098
602d24deec 添加admin-解决方案 2023-11-20 15:38:33 +08:00
Kimdiego2098
a2b9f66785 修复设备curd服务aop失效的问题 2023-11-19 23:30:11 +08:00
Kimdiego2098
7cbf289b50 更新版本号 2023-11-19 22:32:53 +08:00
Kimdiego2098
4097da79a5 modbus server 添加是否立即写入内存的选项 2023-11-19 22:32:13 +08:00
Kimdiego2098
91b7ae554f 默认开启多标签 2023-11-19 22:31:47 +08:00
Kimdiego2098
3121aa2542 添加历史报警插件 2023-11-19 22:19:05 +08:00
Kimdiego2098
4bf895e6e1 修复4.0代码 s7协议未设置适配器导致初始化/读写失败的问题 2023-11-19 21:49:32 +08:00
Kimdiego2098
0c5489e920 增加上传插件 缓存基类 2023-11-19 15:20:01 +08:00
Kimdiego2098
d63c3aaa80 获取插件继承属性时,去除不重写特性的属性 2023-11-19 14:15:58 +08:00
Kimdiego2098
4f188ea6cc 整理文件 2023-11-18 23:04:51 +08:00
Kimdiego2098
acb17018ae 整理文件 2023-11-18 23:04:25 +08:00
Kimdiego2098
2affe2988d 整理文件 2023-11-18 23:03:24 +08:00
Kimdiego2098
4174dd2206 整理文件 2023-11-18 23:03:02 +08:00
Kimdiego2098
e1c492f238 整理文件 2023-11-18 23:02:28 +08:00
Kimdiego2098
fb08e34fa3 整理文件 2023-11-18 23:01:56 +08:00
Kimdiego2098
a1793a0afe 去除不必要的文件 2023-11-18 22:52:26 +08:00
Kimdiego2098
4da9763b49 去除不必要的文件 2023-11-18 22:50:03 +08:00
Kimdiego2098
81e0918bd0 迁移4.0 2023-11-18 22:45:24 +08:00
Kimdiego2098
c1e064f06d 迁移4.0 2023-11-18 22:43:36 +08:00
Kimdiego2098
1c52be8b47 添加停止线程等待时间 2023-11-16 22:32:22 +08:00
Kimdiego2098
bcd82055ca s7握手失败后,手动关闭连接 2023-11-11 10:26:05 +08:00
Kimdiego2098
c47d95d170 fix:修复线程阻塞检测触发重启时,后台变量列表不再刷新的问题! 2023-11-10 09:00:50 +08:00
Kimdiego2098
3e62f1ad51 历史数据上传成功后,才上传缓存数据 2023-11-09 18:55:56 +08:00
Kimdiego2098
8dcae973ef update 2023-11-08 16:19:46 +08:00
Kimdiego2098
4cf35f7294 发布3.0.1版本 2023-11-03 11:32:13 +08:00
Kimdiego2098
94c77d151b update touchsocket 2023-11-03 11:27:12 +08:00
Kimdiego2098
7f600e2b4b update touchsocket 2023-11-03 11:19:26 +08:00
Kimdiego2098
c809d0ba87 不存在插件时,报错内容优化 2023-11-01 14:51:08 +08:00
Kimdiego2098
50f038ec89 update webapiClient 2023-11-01 14:21:53 +08:00
Kimdiego2098
9199a255a2 调整Timeout属性为int数据类型 2023-10-31 23:49:46 +08:00
Kimdiego2098
d324537b47 更新发布版3.0.0.28 2023-10-31 17:47:07 +08:00
Kimdiego2098
d0c05685f7 更新OPCUAClient,订阅模式适配 分组 2023-10-31 10:55:50 +08:00
Kimdiego2098
1063c930b5 update 2023-10-31 00:24:00 +08:00
Kimdiego2098
79cbd44366 tcpservice停止时执行shutdown方法,修复demo发布不显示页面的问题 2023-10-30 21:31:30 +08:00
Kimdiego2098
7fdac1c5cb 添加部分Pro内容 2023-10-29 19:39:38 +08:00
Kimdiego2098
0c0cf72ebb 添加部分Pro内容 2023-10-29 19:34:17 +08:00
Kimdiego2098
8e2fe175ed 更新文档 2023-10-28 22:06:58 +08:00
Kimdiego2098
d1cff037c9 发布3.0.0.27版本;
优化设备线程启停逻辑
添加winform(blazor) demo
部分页面显示内容优化
2023-10-28 22:03:52 +08:00
Kimdiego2098
fc88a2fafa 发布3.0.0.27版本;
优化设备线程启停逻辑
添加winform(blazor) demo
部分页面显示内容优化
2023-10-28 22:00:35 +08:00
Kimdiego2098
45fcceb056 优化设备线程启停逻辑 2023-10-28 21:58:09 +08:00
Kimdiego2098
7043477038 添加部分Pro内容 2023-10-28 21:21:03 +08:00
Kimdiego2098
7dd685cf54 添加winform(blazor) demo 2023-10-28 15:20:12 +08:00
Kimdiego2098
5f5e4969c0 调整pro类库 2023-10-28 10:54:26 +08:00
Diego2098
8a53fd19e9 设备状态页面显示优化 2023-10-27 20:19:26 +08:00
Diego2098
baf4714c36 状态显示:已退出自动更新 2023-10-27 20:18:57 +08:00
Kimdiego2098
7ba9ac7a5b 报文显示限长500 2023-10-27 14:53:19 +08:00
Kimdiego2098
85b8f26e8e tcpserver 报文输出 添加 ip端口 提示 2023-10-27 14:41:35 +08:00
Kimdiego2098
594a0f1410 update the tcpserver class and add log output for start and stop 2023-10-27 14:24:26 +08:00
Kimdiego2098
d317d757d7 优化线程上下文转换 2023-10-26 19:16:24 +08:00
Kimdiego2098
fdf0ba6318 添加ConfigureAwait 2023-10-26 18:06:33 +08:00
Kimdiego2098
15bf7de5fa 优化设备导出逻辑 2023-10-26 15:15:43 +08:00
Kimdiego2098
d3402b058e 优化设备导出逻辑 2023-10-26 15:10:26 +08:00
Kimdiego2098
e7dfdd4031 3.0.0.25 2023-10-26 13:21:40 +08:00
Kimdiego2098
b2dd7b6364 Merge branch 'master' of https://gitee.com/dotnetchina/ThingsGateway 2023-10-26 13:20:54 +08:00
Kimdiego2098
9bd6d9abbf 3.0.0.25 2023-10-26 13:20:31 +08:00
Kimdiego2098
cd14428fea update 2023-10-26 13:19:18 +08:00
Kimdiego2098
19d9f03c2b s7添加错误返回码提示,组包处理 2023-10-26 13:05:06 +08:00
Kimdiego2098
0d57e72bbf 增加硬件信息 相关json配置 2023-10-26 10:54:48 +08:00
Kimdiego2098
329516a61b 更新nuget依赖 2023-10-26 09:37:01 +08:00
Kimdiego2098
d566869589 硬件信息页面添加刷新条件 2023-10-26 09:20:25 +08:00
Kimdiego2098
9cb8d8e6c7 更新文档 2023-10-25 00:53:27 +08:00
Kimdiego2098
9de3c57e5d update touchsocket 2023-10-25 00:53:17 +08:00
Kimdiego2098
f32ff92b0b <Version>3.0.0.24</Version> 2023-10-24 23:48:20 +08:00
Kimdiego2098
88d71e271e 更改S7协议 设备属性,注意删除了DstTSAP,改为较直观的 机架号/槽位号 2023-10-24 23:47:35 +08:00
Kimdiego2098
fd9c14612a 添加属性识别 16进制写入 2023-10-24 23:46:50 +08:00
Kimdiego2098
e26e5a160f 添加nuget依赖包 2023-10-24 20:50:39 +08:00
Kimdiego2098
b836bfed22 更新nuget包 2023-10-24 00:37:46 +08:00
Kimdiego2098
a4b598c6d0 调整解决方案文件夹 2023-10-24 00:13:03 +08:00
Kimdiego2098
c9ab755839 暂时屏蔽mqttserver 遗留消息错误 2023-10-23 20:51:11 +08:00
Kimdiego2098
9920edba53 调整编码 2023-10-23 20:47:00 +08:00
Kimdiego2098
12bd7280d1 调整mqttlog 2023-10-23 20:46:17 +08:00
Kimdiego2098
d30ea7f63b 调整mqttlog 2023-10-23 20:43:58 +08:00
Kimdiego2098
ebd3390db6 添加部分兼容方法 2023-10-22 02:42:16 +08:00
Kimdiego2098
9a374a9ebc 添加部分兼容方法 2023-10-22 02:26:18 +08:00
Kimdiego2098
b1bc22cb08 update touchsocket 2023-10-22 02:26:04 +08:00
Kimdiego2098
4930d53890 调整代码格式 2023-10-21 19:15:26 +08:00
Kimdiego2098
c31327b5bc update touchsocket 2023-10-21 19:08:21 +08:00
Kimdiego2098
3f2aa1f1e1 调整代码格式 2023-10-21 19:03:15 +08:00
Kimdiego2098
6e78c00a96 调整代码格式 2023-10-21 19:02:58 +08:00
Kimdiego2098
c27dde085e 3.0.0.23 2023-10-20 21:38:22 +08:00
Kimdiego2098
d26cc308c0 优化SQLDB实时表模式,插入/更新 2023-10-20 21:38:07 +08:00
Kimdiego2098
fb1efdf290 sqlsugar提示默认中文 2023-10-20 21:37:42 +08:00
Kimdiego2098
3c99f2a472 update touchsocket 2023-10-20 21:03:29 +08:00
Kimdiego2098
affe9a44e0 优化 ModbusServer 内存占用 2023-10-20 01:53:48 +08:00
Kimdiego2098
43730fa519 3.0.0.21 2023-10-20 01:19:18 +08:00
Kimdiego2098
d39aa22b09 优化OPCUAServer,取消注册,提供多url写入 2023-10-20 01:19:05 +08:00
Kimdiego2098
e232a6b6ea 更新赞助名单 2023-10-19 21:15:35 +08:00
Kimdiego2098
71ebb36fe9 更新文档 2023-10-19 20:36:28 +08:00
Kimdiego2098
78a0b86327 更新版本:3.0.0.20 2023-10-19 20:27:31 +08:00
Kimdiego2098
2636c16a97 优化modbusServer内存管理 2023-10-19 20:24:39 +08:00
Kimdiego2098
fd77c0242d update touchsocket 2023-10-19 20:23:43 +08:00
Kimdiego2098
e74819a900 update toucksocket 2023-10-18 21:51:49 +08:00
Kimdiego2098
9b7f696c9b 更新文档 2023-10-18 20:42:10 +08:00
Kimdiego2098
0230d614e7 发布驱动包 2023-10-18 18:04:37 +08:00
Diego2098
252d99ad78 !12 【轻量级 PR】:修正心跳事件中的参数
Merge pull request !12 from youthalan/N/A
2023-10-18 06:59:17 +00:00
youthalan
1ffc200350 修正心跳事件中的参数
Signed-off-by: youthalan <youthalan@126.com>
2023-10-18 06:58:03 +00:00
Kimdiego2098
807d89b2b2 更新版本号 2023-10-18 13:13:03 +08:00
Kimdiego2098
4013afa1f1 初始化 采集/上传线程时 直接返回线程控制 2023-10-18 13:11:59 +08:00
Kimdiego2098
a580927ceb 更新OPCUAClient类库 2023-10-18 13:09:25 +08:00
Kimdiego2098
bf2cf52034 更新OPCUAClient类库 2023-10-18 12:45:26 +08:00
Kimdiego2098
81bb8b7c31 更新OPCUAClient类库 2023-10-18 12:44:58 +08:00
Kimdiego2098
a825007fb5 更新版本号与nuget发布 2023-10-18 12:39:42 +08:00
Kimdiego2098
988124d96a 更新OPCUAClient类库 2023-10-18 12:38:20 +08:00
Diego2098
f0de815296 !11 补充OPCClient类库事件的缺失文件
Merge pull request !11 from youthalan/N/A
2023-10-18 04:34:34 +00:00
youthalan
0e2d58c887 补充OPCClient类库事件的缺失文件
Signed-off-by: youthalan <youthalan@126.com>
2023-10-18 04:32:39 +00:00
Diego2098
b155382626 !10 添加连接或断开事件
Merge pull request !10 from youthalan/N/A
2023-10-18 04:20:22 +00:00
youthalan
f362d740af 修改OPCUAClient类添加连接或断开事件,修改注入时不需要带参数
Signed-off-by: youthalan <youthalan@126.com>
2023-10-18 03:48:05 +00:00
Kimdiego2098
4a85e31a4f update 3.0.0.16 2023-10-18 00:38:36 +08:00
Kimdiego2098
302c270ad5 opcuaClient浏览空间 添加是否显示子变量的选项 2023-10-18 00:36:44 +08:00
Kimdiego2098
3c1517d0f3 更改日志输出内容 2023-10-18 00:15:07 +08:00
Kimdiego2098
f9fb222044 update 3.0.0.15 2023-10-18 00:07:24 +08:00
Kimdiego2098
e8edc02ba3 增加不支持单文件发布的说明 2023-10-17 23:47:24 +08:00
Kimdiego2098
95a44e3053 update tdengineDB plugin 2023-10-17 23:43:48 +08:00
Kimdiego2098
74a9fe9a87 update touchsocket 2023-10-17 23:12:19 +08:00
Kimdiego2098
4d03f9ea1a TD时序库插件,创建时间更改为主键 2023-10-17 23:08:09 +08:00
Kimdiego2098
67c96ca991 update touchsocket 2023-10-17 23:06:21 +08:00
Kimdiego2098
88fb793c68 更新nuget包 2023-10-17 21:00:50 +08:00
Kimdiego2098
d6d02d8cc5 update SQLDB 2023-10-16 20:50:10 +08:00
Kimdiego2098
c5a3f8e2e3 update touchsocket and other 2023-10-16 20:36:51 +08:00
Kimdiego2098
27e8653a1a 3.0.0.13 2023-10-16 17:44:09 +08:00
Kimdiego2098
863beda82c 增加关系库存储插件; 2023-10-16 17:40:17 +08:00
Kimdiego2098
bac84c3ecd 增加时序库存储插件; 2023-10-16 17:40:13 +08:00
Kimdiego2098
2fca2ad9f8 更新pro用户列表 2023-10-16 17:39:14 +08:00
Kimdiego2098
dd75286fe0 3.0.0.12 2023-10-16 08:47:39 +08:00
Kimdiego2098
7f91792cf1 opcuaclient添加是否加载服务端数据类型的选项 2023-10-16 08:46:46 +08:00
Kimdiego2098
0e0ccad311 fix:tcpservice dispose err 2023-10-16 08:46:32 +08:00
Diego2098
0691f72e67 !9 增加可选择安全订阅
Merge pull request !9 from youthalan/N/A
2023-10-16 00:33:19 +00:00
youthalan
7e38a51720 增加可选择安全订阅,以加快订阅速度
Signed-off-by: youthalan <youthalan@126.com>
2023-10-16 00:26:59 +00:00
Kimdiego2098
34ca8243a3 更新3.0.0.11 2023-10-15 20:26:18 +08:00
Diego2098
112fea7632 读取数据类型方法改为批量 2023-10-15 20:21:23 +08:00
Kimdiego2098
378763e4ee 同步Pro版本 2023-10-13 20:14:35 +08:00
Kimdiego2098
517bd0394d update touchsocket 2023-10-13 19:16:12 +08:00
Kimdiego2098
70adb97fb5 update nuget 2023-10-13 16:14:42 +08:00
Kimdiego2098
623d44cabe update 3.0.0.7 2023-10-13 11:54:27 +08:00
Kimdiego2098
0d479ca00b 添加三菱mc 3e帧二进制通讯协议文档 2023-10-13 11:51:51 +08:00
Kimdiego2098
8bc49ef437 update demo 2023-10-12 18:52:44 +08:00
Kimdiego2098
f83fcec786 参数名称修改 2023-10-12 14:57:58 +08:00
Kimdiego2098
93690ce40d fix:mqtt保留消息未更新/更新错误 2023-10-12 14:57:36 +08:00
Kimdiego2098
f82c5f2f27 3.0.0.6 2023-10-11 16:35:10 +08:00
Kimdiego2098
a83c1c3899 update nuget 2023-10-11 16:28:18 +08:00
Kimdiego2098
91d6aed109 调整代码执行顺序 2023-10-11 16:19:34 +08:00
Kimdiego2098
db8f8fe51d update ManageGateway 2023-10-11 10:59:33 +08:00
Kimdiego2098
4596004b17 update ManageGateway 2023-10-11 10:41:09 +08:00
Kimdiego2098
d5540906cb update 3.0.0.5 2023-10-10 21:09:34 +08:00
Kimdiego2098
90796a979d fix:modbusRtu写入返回报文缓存逻辑修复 2023-10-10 21:08:42 +08:00
Kimdiego2098
2190a87772 update touchsocket 2023-10-10 20:03:40 +08:00
Kimdiego2098
c5953b83f8 update touchsocket 2023-10-10 20:02:15 +08:00
Kimdiego2098
24bc60abf0 fix:signalR razor dispose接口 2023-10-09 18:11:23 +08:00
Kimdiego2098
31eee6b009 update uaparser 2023-10-09 10:56:22 +08:00
Kimdiego2098
c5da565a8f 添加MqttRpcDemo 2023-10-07 12:04:17 +08:00
Kimdiego2098
947cd712e1 添加清理日志任务配置参数 2023-10-06 18:28:06 +08:00
Kimdiego2098
edc208f96b update 3.0.0.2 2023-10-05 15:32:50 +08:00
Diego2098
1fb0296ee7 update Directory.Build.props 2023-10-05 15:21:51 +08:00
Kimdiego2098
6488d3df87 修复重启共享通道中的单个设备时,导致通道内其他设备变量异常 2023-10-05 00:34:12 +08:00
Kimdiego2098
56189d78e0 update opcuaclient 2023-10-04 17:12:47 +08:00
Kimdiego2098
bff18127b8 update opcuaclient 2023-10-04 16:59:15 +08:00
Kimdiego2098
363206e0ba update nuget 2023-10-04 02:04:09 +08:00
Kimdiego2098
fd3e378501 update 3.0.0.1 2023-10-04 01:42:27 +08:00
Kimdiego2098
4ba2fe4c9d 实时报警列表线程同步 2023-10-04 01:27:03 +08:00
Kimdiego2098
2c499626ad modbusRtu 适配器 过滤干扰头部数据 2023-10-04 01:26:44 +08:00
Kimdiego2098
2b581a03c3 更新种子数据 2023-10-04 01:24:34 +08:00
Kimdiego2098
450c15210a update windowsService bat 2023-10-03 19:11:42 +08:00
Kimdiego2098
65fed8cc93 update demo 2023-10-03 18:54:01 +08:00
Kimdiego2098
4b64771ea2 rpc调用提示优化 2023-10-03 18:20:39 +08:00
Kimdiego2098
f39977a6ff fix:rpc 特殊方法分类错误 2023-10-03 17:05:49 +08:00
Kimdiego2098
933b535caa update demo 2023-10-02 22:37:50 +08:00
Kimdiego2098
8abc5d2f20 update driverDebugPage 2023-10-02 18:34:55 +08:00
Kimdiego2098
d8783cd994 update opcuaClient 2023-10-02 18:30:58 +08:00
Diego2098
d5d087feb5 add s7 wstring addressSign 2023-10-01 16:54:42 +08:00
Diego2098
6ba3399df7 add s7 wstring addressSign 2023-10-01 16:49:07 +08:00
Diego2098
65124b3aa8 更新demo 2023-10-01 13:33:25 +08:00
Kimdiego2098
98597f4726 update demo csproj 2023-10-01 00:33:33 +08:00
Kimdiego2098
e7981f0d8e add EncodingMapper 2023-10-01 00:25:04 +08:00
Kimdiego2098
cf654427c3 更新文档 2023-09-30 23:28:50 +08:00
Kimdiego2098
ff2f628282 默认不启用远程更新 2023-09-30 23:23:44 +08:00
Kimdiego2098
ae818ca265 更新readme 2023-09-30 23:09:48 +08:00
Kimdiego2098
0f2aed458e 同步3.0.0版本代码 2023-09-30 23:05:53 +08:00
Kimdiego2098
d486c44ff6 更新文档 2023-09-26 20:03:58 +08:00
Kimdiego2098
ca7b9980bf 2.1.0.15 2023-09-20 11:50:06 +08:00
Kimdiego2098
3c71e6a8e3 2.1.0.15 2023-09-20 11:47:10 +08:00
Kimdiego2098
542442864c 添加kafka/mq/iotsharp的间隔上传 2023-09-20 11:46:17 +08:00
Diego2098
5edb64fa85 fix:上传设备选择驱动时没有正确刷新 2023-09-18 00:10:02 +08:00
Kimdiego2098
8dc1c898a3 2.1.0.14 2023-09-15 14:02:15 +08:00
Kimdiego2098
1ed35726b0 ExpressionEvaluator改为多实例 2023-09-15 14:01:54 +08:00
1857 changed files with 129248 additions and 56664 deletions

2
.gitignore vendored
View File

@@ -363,4 +363,6 @@ MigrationBackup/
FodyWeavers.xsd
/framework/*pro*
/framework/*Pro*

View File

@@ -7,7 +7,7 @@
**ThingsGateway** 存储库同时提供 [**设备采集驱动**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22)
**ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 [**ThingsGateway.Admin**](https://gitee.com/dotnetchina/ThingsGateway/blob/master/framework/ThingsGateway.Admin.sln)
**ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 **ThingsGateway - Admin**
## 文档

View File

@@ -1,149 +0,0 @@
[*.cs]
# CA1822: 将成员标记为 static
dotnet_diagnostic.CA1822.severity = none
# CA1816: Dispose 方法应调用 SuppressFinalize
dotnet_diagnostic.CA1816.severity = none
# CA2254: 模板应为静态表达式
dotnet_diagnostic.CA2254.severity = none
[*.cs]
#### 命名样式 ####
# 命名规则
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# 符号规范
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# 命名样式
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
csharp_using_directive_placement = outside_namespace:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_conditional_delegate_call = true:suggestion
csharp_style_var_for_built_in_types = false:silent
csharp_style_var_when_type_is_apparent = false:silent
csharp_style_var_elsewhere = false:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
# CA2208: 正确实例化参数异常
dotnet_diagnostic.CA2208.severity = none
# IDE0057: 使用范围运算符
dotnet_diagnostic.IDE0057.severity = none
# IDE0056: 使用索引运算符
dotnet_diagnostic.IDE0056.severity = none
# CA1830: 最好使用 StringBuilder 的强类型 Append 和 Insert 方法重载
dotnet_diagnostic.CA1830.severity = none
# CA1847: 将字符型文本用于单个字符查找
dotnet_diagnostic.CA1847.severity = none
[*.vb]
#### 命名样式 ####
# 命名规则
dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion
dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface
dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
# 符号规范
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.类型.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
dotnet_naming_symbols.类型.required_modifiers =
dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method
dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
dotnet_naming_symbols.非字段成员.required_modifiers =
# 命名样式
dotnet_naming_style.以_i_开始.required_prefix = I
dotnet_naming_style.以_i_开始.required_suffix =
dotnet_naming_style.以_i_开始.word_separator =
dotnet_naming_style.以_i_开始.capitalization = pascal_case
dotnet_naming_style.帕斯卡拼写法.required_prefix =
dotnet_naming_style.帕斯卡拼写法.required_suffix =
dotnet_naming_style.帕斯卡拼写法.word_separator =
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
dotnet_naming_style.帕斯卡拼写法.required_prefix =
dotnet_naming_style.帕斯卡拼写法.required_suffix =
dotnet_naming_style.帕斯卡拼写法.word_separator =
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
[*.{cs,vb}]
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
end_of_line = crlf
# IDE0060: 删除未使用的参数
dotnet_diagnostic.IDE0060.severity = none

View File

@@ -1,30 +0,0 @@
<Project>
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Version>2.1.0.13</Version>
<Authors>Diego</Authors>
<Product>ThingsGateway</Product>
<Copyright>© 2023-present Diego</Copyright>
<RepositoryUrl>https://gitee.com/diego2098/ThingsGateway</RepositoryUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSource>true</EmbedUntrackedSource>
<EmbedAllSources>true</EmbedAllSources>
<RepositoryType>Gitee</RepositoryType>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<SignAssembly>True</SignAssembly>
<DelaySign>False</DelaySign>
<SatelliteResourceLanguages>zh-Hans</SatelliteResourceLanguages>
</PropertyGroup>
<PropertyGroup>
<DocumentationFile>$(MSBuildProjectName).xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<LangVersion>latest</LangVersion>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,101 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33927.249
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "admin", "admin", "{4E66C22C-0636-4949-BF6A-9E3BBE1550BA}"
ProjectSection(SolutionItems) = preProject
admin\Directory.Build.props = admin\Directory.Build.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Components", "admin\ThingsGateway.Components\ThingsGateway.Components.csproj", "{0A891D8E-23B3-46AD-8D30-565EE5004F93}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Core", "admin\ThingsGateway.Core\ThingsGateway.Core.csproj", "{A712EAEE-94F2-4F01-8C1C-2EC802280DD7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Core", "admin\ThingsGateway.Admin.Core\ThingsGateway.Admin.Core.csproj", "{5DA3D2BD-6768-4479-B52F-49E022EFF310}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Blazor", "admin\ThingsGateway.Admin.Blazor\ThingsGateway.Admin.Blazor.csproj", "{8DD5DF98-7FDE-4B49-8661-AEB44D923CFE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Application", "admin\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj", "{D6685A42-2712-417A-92C5-5EFF90B9FA94}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.ApiController", "admin\ThingsGateway.Admin.ApiController\ThingsGateway.Admin.ApiController.csproj", "{0D17D801-6DAA-4FD1-9A99-F9F07FA6BA88}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "web", "web", "{F0C9A8CB-231B-45E0-B91B-4FEF7EF47197}"
ProjectSection(SolutionItems) = preProject
web\Directory.Build.props = web\Directory.Build.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Core", "web\ThingsGateway.Web.Core\ThingsGateway.Web.Core.csproj", "{D37EC028-EA46-4510-8261-6E780A906314}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Entry", "web\ThingsGateway.Web.Entry\ThingsGateway.Web.Entry.csproj", "{C5F662EB-991F-438D-BF61-EF87E7371C04}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "解决方案项", "解决方案项", "{97B23D8B-C6C0-4746-A21F-C7B49354B284}"
ProjectSection(SolutionItems) = preProject
..\.gitignore = ..\.gitignore
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Foundation", "foundation\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj", "{6961511A-8787-42AF-827D-B630B2AF4791}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "foundation", "foundation", "{268A1A81-2685-47E1-9986-5934A58A31A4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0A891D8E-23B3-46AD-8D30-565EE5004F93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A891D8E-23B3-46AD-8D30-565EE5004F93}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A891D8E-23B3-46AD-8D30-565EE5004F93}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A891D8E-23B3-46AD-8D30-565EE5004F93}.Release|Any CPU.Build.0 = Release|Any CPU
{A712EAEE-94F2-4F01-8C1C-2EC802280DD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A712EAEE-94F2-4F01-8C1C-2EC802280DD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A712EAEE-94F2-4F01-8C1C-2EC802280DD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A712EAEE-94F2-4F01-8C1C-2EC802280DD7}.Release|Any CPU.Build.0 = Release|Any CPU
{5DA3D2BD-6768-4479-B52F-49E022EFF310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5DA3D2BD-6768-4479-B52F-49E022EFF310}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5DA3D2BD-6768-4479-B52F-49E022EFF310}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5DA3D2BD-6768-4479-B52F-49E022EFF310}.Release|Any CPU.Build.0 = Release|Any CPU
{8DD5DF98-7FDE-4B49-8661-AEB44D923CFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8DD5DF98-7FDE-4B49-8661-AEB44D923CFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8DD5DF98-7FDE-4B49-8661-AEB44D923CFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8DD5DF98-7FDE-4B49-8661-AEB44D923CFE}.Release|Any CPU.Build.0 = Release|Any CPU
{D6685A42-2712-417A-92C5-5EFF90B9FA94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6685A42-2712-417A-92C5-5EFF90B9FA94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6685A42-2712-417A-92C5-5EFF90B9FA94}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6685A42-2712-417A-92C5-5EFF90B9FA94}.Release|Any CPU.Build.0 = Release|Any CPU
{0D17D801-6DAA-4FD1-9A99-F9F07FA6BA88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D17D801-6DAA-4FD1-9A99-F9F07FA6BA88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D17D801-6DAA-4FD1-9A99-F9F07FA6BA88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D17D801-6DAA-4FD1-9A99-F9F07FA6BA88}.Release|Any CPU.Build.0 = Release|Any CPU
{D37EC028-EA46-4510-8261-6E780A906314}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D37EC028-EA46-4510-8261-6E780A906314}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D37EC028-EA46-4510-8261-6E780A906314}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D37EC028-EA46-4510-8261-6E780A906314}.Release|Any CPU.Build.0 = Release|Any CPU
{C5F662EB-991F-438D-BF61-EF87E7371C04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5F662EB-991F-438D-BF61-EF87E7371C04}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5F662EB-991F-438D-BF61-EF87E7371C04}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5F662EB-991F-438D-BF61-EF87E7371C04}.Release|Any CPU.Build.0 = Release|Any CPU
{6961511A-8787-42AF-827D-B630B2AF4791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6961511A-8787-42AF-827D-B630B2AF4791}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6961511A-8787-42AF-827D-B630B2AF4791}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6961511A-8787-42AF-827D-B630B2AF4791}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0A891D8E-23B3-46AD-8D30-565EE5004F93} = {4E66C22C-0636-4949-BF6A-9E3BBE1550BA}
{A712EAEE-94F2-4F01-8C1C-2EC802280DD7} = {4E66C22C-0636-4949-BF6A-9E3BBE1550BA}
{5DA3D2BD-6768-4479-B52F-49E022EFF310} = {4E66C22C-0636-4949-BF6A-9E3BBE1550BA}
{8DD5DF98-7FDE-4B49-8661-AEB44D923CFE} = {4E66C22C-0636-4949-BF6A-9E3BBE1550BA}
{D6685A42-2712-417A-92C5-5EFF90B9FA94} = {4E66C22C-0636-4949-BF6A-9E3BBE1550BA}
{0D17D801-6DAA-4FD1-9A99-F9F07FA6BA88} = {4E66C22C-0636-4949-BF6A-9E3BBE1550BA}
{D37EC028-EA46-4510-8261-6E780A906314} = {F0C9A8CB-231B-45E0-B91B-4FEF7EF47197}
{C5F662EB-991F-438D-BF61-EF87E7371C04} = {F0C9A8CB-231B-45E0-B91B-4FEF7EF47197}
{6961511A-8787-42AF-827D-B630B2AF4791} = {268A1A81-2685-47E1-9986-5934A58A31A4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C49B2D3E-6818-4E28-91B7-6E4E7E264BBB}
EndGlobalSection
EndGlobal

View File

@@ -1,66 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DynamicApiController;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel;
namespace ThingsGateway.Admin.Application
{
/// <summary>
/// OpenApi登录控制器
/// </summary>
[ApiDescriptionSettings(CateGoryConst.ThingsGatewayOpenApi, Order = 200)]
[Route("auth/openapi")]
[LoggingMonitor]
[Description("OpenApi登录")]
[Authorize(AuthenticationSchemes = "Bearer")]
public class OpenApiAuthController : IDynamicApiController
{
private readonly IOpenApiAuthService _authService;
/// <summary>
/// <inheritdoc cref="OpenApiAuthController"/>
/// </summary>
/// <param name="authService"></param>
public OpenApiAuthController(IOpenApiAuthService authService)
{
_authService = authService;
}
/// <summary>
/// OpenApi登录
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("login")]
[Description(EventSubscriberConst.LoginOpenApi)]
public async Task<LoginOpenApiOutput> LoginOpenApiAsync(LoginOpenApiInput input)
{
return await _authService.LoginOpenApiAsync(input);
}
/// <summary>
/// 登出
/// </summary>
/// <returns></returns>
[HttpPost("logout")]
[Description(EventSubscriberConst.LogoutOpenApi)]
public async Task LogoutAsync()
{
await _authService.LogoutAsync();
}
}
}

View File

@@ -1,71 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Furion.DynamicApiController;
using Furion.SpecificationDocument;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application
{
/// <summary>
/// Swagger登录授权服务
/// </summary>
[ApiDescriptionSettings(CateGoryConst.ThingsGatewayAdmin, Order = 200)]
[Route("Swagger")]
public class SwaggerController : IDynamicApiController, IScoped
{
private readonly ConfigService _configService;
/// <summary>
/// <inheritdoc cref="SwaggerController"/>
/// </summary>
/// <param name="sysConfigService"></param>
public SwaggerController(ConfigService sysConfigService)
{
_configService = sysConfigService;
}
/// <summary>
/// Swagger登录检查
/// </summary>
/// <returns></returns>
[HttpPost("CheckUrl")]
[AllowAnonymous, NonUnify]
public async Task<int> SwaggerCheckUrlAsync()
{
var enable = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SWAGGERLOGIN_OPEN)).ConfigValue.ToBoolean();
return enable ? 401 : 200;
}
/// <summary>
/// Swagger登录
/// </summary>
/// <param name="auth"></param>
/// <returns></returns>
[HttpPost("SubmitUrl")]
[AllowAnonymous, NonUnify]
public async Task<int> SwaggerSubmitUrlAsync([FromForm] SpecificationAuth auth)
{
var userName = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SWAGGER_NAME)).ConfigValue;
var password = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SWAGGER_PASSWORD)).ConfigValue;
if (auth.UserName == userName && auth.Password == password)
{
return 200;
}
return 401;
}
}
}

View File

@@ -1,102 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>ThingsGateway.Admin.ApiController</name>
</assembly>
<members>
<member name="T:ThingsGateway.Admin.Application.AuthController">
<summary>
后台登录控制器
</summary>
</member>
<member name="M:ThingsGateway.Admin.Application.AuthController.#ctor(ThingsGateway.Admin.Application.IAuthService)">
<summary>
<inheritdoc cref="T:ThingsGateway.Admin.Application.AuthController"/>
</summary>
<param name="authService"></param>
</member>
<member name="M:ThingsGateway.Admin.Application.AuthController.LoginAsync(ThingsGateway.Admin.Application.LoginInput)">
<summary>
后台登录
</summary>
<param name="input"></param>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Application.AuthController.LogoutAsync">
<summary>
后台登出
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Application.FileController">
<summary>
文件下载
</summary>
</member>
<member name="M:ThingsGateway.Admin.Application.FileController.#ctor(ThingsGateway.Admin.Application.IFileService,ThingsGateway.Admin.Application.IOperateLogService,ThingsGateway.Admin.Application.IVisitLogService)">
<summary>
<inheritdoc cref="T:ThingsGateway.Admin.Application.FileController"/>
</summary>
</member>
<member name="M:ThingsGateway.Admin.Application.FileController.DownloadOperateLogAsync(ThingsGateway.Admin.Application.OperateLogInput)">
<summary>
下载操作日志
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Application.FileController.DownloadVisitLogAsync(ThingsGateway.Admin.Application.VisitLogInput)">
<summary>
下载访问日志
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Application.OpenApiAuthController">
<summary>
OpenApi登录控制器
</summary>
</member>
<member name="M:ThingsGateway.Admin.Application.OpenApiAuthController.#ctor(ThingsGateway.Admin.Application.IOpenApiAuthService)">
<summary>
<inheritdoc cref="T:ThingsGateway.Admin.Application.OpenApiAuthController"/>
</summary>
<param name="authService"></param>
</member>
<member name="M:ThingsGateway.Admin.Application.OpenApiAuthController.LoginOpenApiAsync(ThingsGateway.Admin.Application.LoginOpenApiInput)">
<summary>
OpenApi登录
</summary>
<param name="input"></param>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Application.OpenApiAuthController.LogoutAsync">
<summary>
登出
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Application.SwaggerController">
<summary>
Swagger登录授权服务
</summary>
</member>
<member name="M:ThingsGateway.Admin.Application.SwaggerController.#ctor(ThingsGateway.Admin.Application.ConfigService)">
<summary>
<inheritdoc cref="T:ThingsGateway.Admin.Application.SwaggerController"/>
</summary>
<param name="sysConfigService"></param>
</member>
<member name="M:ThingsGateway.Admin.Application.SwaggerController.SwaggerCheckUrlAsync">
<summary>
Swagger登录检查
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Application.SwaggerController.SwaggerSubmitUrlAsync(Furion.SpecificationDocument.SpecificationAuth)">
<summary>
Swagger登录
</summary>
<param name="auth"></param>
<returns></returns>
</member>
</members>
</doc>

View File

@@ -1,104 +0,0 @@
{
"RECORDS": [
{
"Id": "22222222222222",
"Category": "SYS_CONFIGBASEDEFAULT",
"ConfigKey": "CONFIG_SWAGGER_NAME",
"ConfigValue": "admin",
"Remark": "swagger账号",
"SortCode": "1",
"IsDelete": "false"
},
{
"Id": "22222222222223",
"Category": "SYS_CONFIGBASEDEFAULT",
"ConfigKey": "CONFIG_SWAGGER_PASSWORD",
"ConfigValue": "123456",
"Remark": "swagger密码",
"SortCode": "2",
"IsDelete": "false"
},
{
"Id": "22222222222224",
"Category": "SYS_CONFIGBASEDEFAULT",
"ConfigKey": "CONFIG_SWAGGERLOGIN_OPEN",
"ConfigValue": "false",
"Remark": "swagger开启登录",
"SortCode": "3",
"IsDelete": "false"
},
{
"Id": "22222222222226",
"Category": "SYS_CONFIGBASEDEFAULT",
"ConfigKey": "CONFIG_TITLE",
"ConfigValue": "ThingsGateway",
"Remark": "标题",
"SortCode": "5",
"IsDelete": "false"
},
{
"Id": "22222222222228",
"Category": "SYS_CONFIGBASEDEFAULT",
"ConfigKey": "CONFIG_COPYRIGHT",
"ConfigValue": "ThingsGateway ©2023 Diego",
"Remark": "系统版权",
"SortCode": "6",
"IsDelete": "false"
},
{
"Id": "22222222222229",
"Category": "SYS_CONFIGBASEDEFAULT",
"ConfigKey": "CONFIG_COPYRIGHT_URL",
"ConfigValue": "https://gitee.com/diego2098/ThingsGateway",
"Remark": "系统版权链接地址",
"SortCode": "7",
"IsDelete": "false"
},
{
"Id": "22222222222231",
"Category": "SYS_CONFIGBASEDEFAULT",
"ConfigKey": "CONFIG_PASSWORD",
"ConfigValue": "111111",
"Remark": "默认用户密码",
"SortCode": "8",
"IsDelete": "false"
},
{
"Id": "22222222222227",
"Category": "SYS_CONFIGBASEDEFAULT",
"ConfigKey": "CONFIG_VERIFICAT_EXPIRES",
"ConfigValue": "14400",
"Remark": "Verificat过期时间(分)",
"SortCode": "9",
"IsDelete": "false"
},
{
"Id": "22222222222232",
"Category": "SYS_CONFIGBASEDEFAULT",
"ConfigKey": "CONFIG_SINGLE_OPEN",
"ConfigValue": "false",
"Remark": "单用户登录开关",
"SortCode": "10",
"IsDelete": "false"
},
{
"Id": "22222222222230",
"Category": "SYS_CONFIGBASEDEFAULT",
"ConfigKey": "CONFIG_CAPTCHA_OPEN",
"ConfigValue": "true",
"Remark": "登录验证码开关",
"SortCode": "11",
"IsDelete": "false"
},
{
"Id": "22222222222225",
"Category": "SYS_CONFIGBASEDEFAULT",
"ConfigKey": "CONFIG_REMARK",
"ConfigValue": "边缘采集网关",
"Remark": "说明",
"SortCode": "12",
"IsDelete": "false"
}
]
}

View File

@@ -1,16 +0,0 @@
{
"RECORDS": [
{
"Id": "212725263001001",
"Code": "superAdmin",
"Name": "超级管理员",
"SortCode": "1"
},
{
"Id": "212725263001002",
"Code": "admin",
"Name": "业务管理员",
"SortCode": "2"
}
]
}

View File

@@ -1,34 +0,0 @@
{
"RECORDS": [
{
"Id": "212725263002001",
"Account": "superAdmin",
"LastLoginDevice": "PC",
"LastLoginIp": "0.0.0.1",
"LastLoginTime": "2023-03-03 21:18:43.7092169",
"LatestLoginDevice": "PC",
"LatestLoginIp": "0.0.0.1",
"LatestLoginTime": "2023-03-03 21:19:16.1043309",
"Password": "7DA385A25A98388E",
"SortCode": "1",
"UserEnable": "true",
"IsDelete": "false",
"UpdateTime": "2023-03-03 21:19:16.1202211"
},
{
"Id": "201725263002001",
"Account": "admin",
"LastLoginDevice": "PC",
"LastLoginIp": "0.0.0.1",
"LastLoginTime": "2023-03-03 18:20:49.1875384",
"LatestLoginDevice": "PC",
"LatestLoginIp": "0.0.0.1",
"LatestLoginTime": "2023-03-03 18:23:08.6424099",
"Password": "7DA385A25A98388E",
"SortCode": "2",
"UserEnable": "true",
"IsDelete": "false",
"UpdateTime": "2023-03-03 18:23:08.6727296"
}
]
}

View File

@@ -1,35 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
</PropertyGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<Content Include="SeedData\Json\sys_config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="SeedData\Json\sys_relation.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="SeedData\Json\sys_resource.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="SeedData\Json\sys_role.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="SeedData\Json\sys_user.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Admin.Core\ThingsGateway.Admin.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,42 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@inject NavigationManager NavigationManager
@namespace ThingsGateway.Admin.Blazor.Core
@inherits BaseComponentBase
@using BlazorComponent;
@using Masa.Blazor;
<div class="d-flex align-center py-1">
<MBreadcrumbs Routable @key="@(Guid.NewGuid())" Class="pa-0">
<DividerContent>
<MIcon Class="ma-0 pa-0">mdi-chevron-right</MIcon>
</DividerContent>
<ChildContent>
<div style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;">
<MBreadcrumbsItem Href="javascript:history.back(-1)">
<MIcon Size=20>mdi-arrow-left</MIcon>
</MBreadcrumbsItem>
@for (var i = 0; i < BreadcrumbItems.Count; i++)
{
var item = BreadcrumbItems[i];
var isLast = i == BreadcrumbItems.Count - 1;
<MBreadcrumbsItem Href="@item.Href">
<span class="@(isLast ? "text-subtitle2" : "text-body2")">@item.Text</span>
</MBreadcrumbsItem>
}
</div>
</ChildContent>
</MBreadcrumbs>
</div>

View File

@@ -1,60 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@inject NavigationManager NavigationManager
@namespace ThingsGateway.Admin.Blazor.Core
@inject UserResoures UserResoures;
@inherits BaseComponentBase
@using BlazorComponent;
@using Masa.Blazor;
<div class="ml-16">
<MMenu OffsetY Bottom Right CloseOnContentClick="true" @bind-Value="_open" MinWidth="@("auto")">
<ActivatorContent>
<MTooltip Color="primary" Bottom>
<ActivatorContent Context="tooltipContext">
@{
var attrs = new Dictionary<string, object>();
foreach (var (key, value) in context.Attrs)
{
attrs.Add(key, value);
}
foreach (var (key, value) in tooltipContext.Attrs)
{
if (attrs.ContainsKey(key) is false) attrs.Add(key, value);
}
}
<MIcon @attributes="@attrs" Size=20 Color="dark-yellow">mdi-star-outline</MIcon>
</ActivatorContent>
<ChildContent>
<span>收藏</span>
</ChildContent>
</MTooltip>
</ActivatorContent>
<ChildContent>
<MList Class="pb-1" Style="min-width:320px;">
@foreach (var nav in UserResoures.WorkbenchOutputs)
{
<MListItem Dense OnClick="()=> NavigationManager.NavigateTo(nav.Component)" Class="px-4">
<MListItemAction Class="mr-3">
<MIcon Size=20 Color="neutral-lighten-3">@nav.Icon</MIcon>
</MListItemAction>
<MListItemContent>
<span Class="text-btn">@(nav.Title)</span>
</MListItemContent>
</MListItem>
}
</MList>
</ChildContent>
</MMenu>
</div>

View File

@@ -1,62 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BlazorComponent;
using Masa.Blazor;
using Microsoft.AspNetCore.Components;
using ThingsGateway.Admin.Core;
using ThingsGateway.Admin.Core.JsonExtensions;
namespace ThingsGateway.Admin.Blazor.Core;
/// <summary>
/// UserMenu
/// </summary>
public partial class UserMenu
{
[Inject]
NavigationManager NavigationManager { get; set; }
[Inject]
private UserResoures UserResoures { get; set; }
[Inject]
private AjaxService AjaxService { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
}
private async Task LogoutAsync()
{
var ajaxOption = new AjaxOption
{
Url = "/auth/b/logout",
};
var str = await AjaxService.GetMessageAsync(ajaxOption);
var ret = str?.ToJsonWithT<UnifyResult<string>>();
if (ret?.Code != 200)
{
await PopupService.EnqueueSnackbarAsync("注销失败", AlertTypes.Error);
}
else
{
await PopupService.EnqueueSnackbarAsync("注销成功", AlertTypes.Success);
await Task.Delay(500);
NavigationManager.NavigateTo(NavigationManager.Uri);
}
}
}

View File

@@ -1,75 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BlazorComponent;
using Furion;
using Masa.Blazor;
using Masa.Blazor.Presets;
using Microsoft.Extensions.DependencyInjection;
namespace ThingsGateway.Admin.Blazor.Core;
/// <summary>
/// AppStartup启动类
/// </summary>
public class Startup : AppStartup
{
/// <inheritdoc/>
public void ConfigureServices(IServiceCollection services)
{
services.AddMasaBlazor(options =>
{
options.Defaults = new Dictionary<string, IDictionary<string, object>>()
{
{
PopupComponents.SNACKBAR, new Dictionary<string, object>()
{
{ nameof(PEnqueuedSnackbars.Closeable), true },
{ nameof(PEnqueuedSnackbars.Position), SnackPosition.TopCenter }
}
}
};
options.ConfigureTheme(theme =>
{
theme.Themes.Dark.Accent = "#FF4081";
theme.Themes.Dark.Error = "#FF5252";
theme.Themes.Dark.Info = "#2196F3";
theme.Themes.Dark.Primary = "#2196F3";
theme.Themes.Dark.Secondary = "#424242";
theme.Themes.Dark.Success = "#4CAF50";
theme.Themes.Dark.Warning = "#FB8C00";
theme.Themes.Dark.UserDefined.Add("barcolor", "#1e1e1e");
theme.Themes.Light.Accent = "#82B1FF";
theme.Themes.Light.Error = "#FF5252";
theme.Themes.Light.Info = "#2196F3";
theme.Themes.Light.Primary = "#1976D2";
theme.Themes.Light.Secondary = "#424242";
theme.Themes.Light.Success = "#4CAF50";
theme.Themes.Light.Warning = "#FB8C00";
theme.Themes.Light.UserDefined.Add("barcolor", "#fff");
});
options.Locale = new Locale("zh-CN", "en-US");
});
services.AddScoped<InitTimezone>();
services.AddScoped<AjaxService>();
services.AddScoped<UserResoures>();
}
}

View File

@@ -1,27 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
</PropertyGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Masa.Blazor" Version="1.0.4" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.11" />
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -1,894 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>ThingsGateway.Admin.Blazor.Core</name>
</assembly>
<members>
<member name="T:ThingsGateway.Admin.Blazor.Core.BaseComponentBase">
<summary>
Razor组件
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.BaseComponentBase.PopupService">
<summary>
弹出层服务
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.BaseComponentBase.Dispose">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.BaseComponentBase.InvokeStateHasChangedAsync">
<summary>
InvokeAsync(StateHasChanged)
</summary>
<returns></returns>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.BaseComponentBase.IsMobile">
<summary>
是否手机端
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.BaseComponentBase.Changed">
<summary>
主动更新
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.AppBarItems">
<summary>
AppBarItems
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppBarItems.CONFIG_COPYRIGHT_URL">
<summary>
链接
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppBarItems.CONFIG_COPYRIGHT">
<summary>
版权
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppBarItems.CONFIG_TITLE">
<summary>
标题
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.Filters">
<summary>
过滤选择Model
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.Filters.Key">
<summary>
DateTable Value
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.Filters.Title">
<summary>
DateTable Text
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.Filters.Value">
<summary>
是否显示
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.PageSize">
<summary>
分页选择Model
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.PageSize.Key">
<summary>
显示
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.PageSize.Value">
<summary>
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker">
<summary>
DateTimePicker
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.Max">
<summary>
max time [utc]
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.Min">
<summary>
min time [utc]
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.NoTitle">
<summary>
NoTitle
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.Value">
<summary>
selected datetime[utc]
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.ValueChanged">
<summary>
ValueChanged
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.ChildContent">
<summary>
ChildContent
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.OutputTimezoneOffset">
<summary>
OutputTimezoneOffset
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.DisplayTimezoneOffset">
<summary>
DisplayTimezoneOffset
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView)">
<summary>
<inheritdoc/>
</summary>
<param name="parameters"></param>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.UpdateValueAsync(System.Nullable{System.DateTime})">
<summary>
</summary>
<param name="dateTime">accept the time using display time zone</param>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.AppListGroup`1">
<summary>
AppListGroup
</summary>
<typeparam name="TItem"></typeparam>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppListGroup`1.Icon">
<summary>
icon
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppListGroup`1.Item">
<summary>
item
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppListGroup`1.SubGroup">
<summary>
sub
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AppListGroup`1.OnInitialized">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.Breadcrumb">
<summary>
Breadcrumb
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.Breadcrumb.Dispose">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.Breadcrumb.OnInitialized">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.EnableChip">
<summary>
启用/停用 文本提示
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.EnableChip.Class">
<summary>
Class
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.EnableChip.Style">
<summary>
Style
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.EnableChip.Value">
<summary>
Value
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.EnableChip.DisabledLabel">
<summary>
DisabledLabel
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.EnableChip.EnabledLabel">
<summary>
EnabledLabel
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.Favorite">
<summary>
收藏/快捷方式
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.Foter">
<summary>
Foter
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.Foter.CONFIG_COPYRIGHT_URL">
<summary>
链接
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.Foter.CONFIG_COPYRIGHT">
<summary>
版权
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.Foter.CONFIG_TITLE">
<summary>
标题
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.Foter.OnParametersSetAsync">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.Logo">
<summary>
Logo
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.Logo.HeightInt">
<summary>
Logo高度
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.Logo.CONFIG_COPYRIGHT_URL">
<summary>
链接
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.Logo.CONFIG_COPYRIGHT">
<summary>
版权
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.Logo.CONFIG_TITLE">
<summary>
标题
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.AppItem">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Children">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Divider">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.HasChildren">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Heading">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Href">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Icon">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.SubTitle">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Target">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Title">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Value">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.IAppItem`1">
<summary>
ListItem
</summary>
<typeparam name="TItem"></typeparam>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Children">
<summary>
子菜单
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Divider">
<summary>
是否启用下划线
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Heading">
<summary>
菜单头部标题
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Href">
<summary>
链接
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Icon">
<summary>
图标
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.SubTitle">
<summary>
菜单副标题
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Target">
<summary>
跳转方式
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Title">
<summary>
菜单标题
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Value">
<summary>
菜单值
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.IAppItem`1.HasChildren">
<summary>
是否有子菜单
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.NavItem">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.NavItem.#ctor">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Children">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Divider">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Group">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Heading">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Href">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Icon">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Segment">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.State">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.SubTitle">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Target">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Title">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Value">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.PageTabs">
<summary>
PageTabs
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.PageTabs.PPageTabs">
<summary>
Tabs实例
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.PageTabs.SelfPatterns">
<summary>
SelfPatterns
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.PageTabs.ChildContent">
<summary>
子组件
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.Search">
<summary>
Search
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.Search.OnParametersSet">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.SysSignalR">
<summary>
SignalR连接
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.SysSignalR.DisposeAsync">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.SysSignalR.OnAfterRenderAsync(System.Boolean)">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.UserMenu">
<summary>
UserMenu
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.UserMenu.OnInitialized">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst">
<summary>
资源标识常量
</summary>
</member>
<member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.ResourceUrl">
<summary>
资源默认路径
</summary>
</member>
<member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.DataTableActions">
<summary>
表格操作列标识
</summary>
</member>
<member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.ThemeCookieKey">
<summary>
主题Cookie
</summary>
</member>
<member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.AppBarHeight">
<summary>
AppBarHeight
</summary>
</member>
<member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.PageTabsHeight">
<summary>
Tab高度
</summary>
</member>
<member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.FooterHeight">
<summary>
FooterHeight
</summary>
</member>
<member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.DefaultHeight">
<summary>
DefaultHeight
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.MenuExtensions">
<summary>
菜单扩展
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.MenuExtensions.Parse(System.Collections.Generic.List{ThingsGateway.Admin.Core.SysResource})">
<summary>
转化为NavItem
</summary>
<param name="menus"></param>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.PopupServiceExtensions">
<summary>
扩展方法
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.PopupServiceExtensions.OpenConfirmDialogAsync(Masa.Blazor.IPopupService,System.String,System.String)">
<summary>
确认弹窗默认Error
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.PopupServiceExtensions.OpenConfirmDialogAsync(Masa.Blazor.IPopupService,System.String,System.String,BlazorComponent.AlertTypes)">
<summary>
确认弹窗
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.PopupServiceExtensions.OpenInformationMessageAsync(Masa.Blazor.IPopupService,System.String)">
<summary>
消息提示
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.Ajax">
<summary>
Ajax组件
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.Ajax.JSRuntime">
<summary>
获得/设置 IJSRuntime 实例
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.Ajax.Dispose">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.Ajax.GetMessageAsync(ThingsGateway.Admin.Blazor.Core.AjaxOption)">
<summary>
请求并返回消息
</summary>
<param name="option">Ajax配置</param>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.Ajax.OnAfterRenderAsync(System.Boolean)">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.Ajax.OnInitialized">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.AjaxOption">
<summary>
Ajax配置类
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AjaxOption.Data">
<summary>
获取/设置 要上传的参数类
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AjaxOption.Method">
<summary>
获取/设置 传输方式默认为POST
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AjaxOption.Url">
<summary>
获取/设置 请求的URL
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.AjaxService">
<summary>
Ajax服务类
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AjaxService.Cache">
<summary>
获得 回调委托缓存集合
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AjaxService.GotoCache">
<summary>
获得 跳转其他页面的回调委托缓存集合
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AjaxService.DownFileCache">
<summary>
获得 下载委托缓存集合
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.GetMessageAsync(ThingsGateway.Admin.Blazor.Core.AjaxOption)">
<summary>
调用Ajax方法发送请求
</summary>
<param name="option"></param>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.DownFileAsync(System.String,System.String,System.Object)">
<summary>
调用Ajax方法发送请求
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.GotoAsync(System.String)">
<summary>
调用 Goto 方法跳转其他页面
</summary>
<param name="url"></param>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.Register(Microsoft.AspNetCore.Components.IComponent,System.Func{ThingsGateway.Admin.Blazor.Core.AjaxOption,System.Threading.Tasks.Task{System.String}})">
<summary>
注册服务
</summary>
<param name="key"></param>
<param name="callback"></param>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.RegisterGoto(Microsoft.AspNetCore.Components.IComponent,System.Func{System.String,System.Threading.Tasks.Task})">
<summary>
注册服务
</summary>
<param name="key"></param>
<param name="callback"></param>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.RegisterDownFile(Microsoft.AspNetCore.Components.IComponent,System.Func{System.String,System.String,System.Object,System.Threading.Tasks.Task})">
<summary>
注册服务
</summary>
<param name="key"></param>
<param name="callback"></param>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.UnRegister(Microsoft.AspNetCore.Components.IComponent)">
<summary>
注销事件
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.UnRegisterGoto(Microsoft.AspNetCore.Components.IComponent)">
<summary>
注销事件
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.UnRegisterDownFile(Microsoft.AspNetCore.Components.IComponent)">
<summary>
注销事件
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent">
<summary>
动态组件类
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent.#ctor(System.Type,System.Collections.Generic.IDictionary{System.String,System.Object})">
<summary>
构造函数
</summary>
<param name="componentType"></param>
<param name="parameters">TCom 组件所需要的参数集合</param>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent.ComponentType">
<summary>
获得/设置 组件类型
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent.Parameters">
<summary>
获得/设置 组件参数集合
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent.CreateComponent``1(System.Collections.Generic.IDictionary{System.String,System.Object})">
<summary>
创建自定义组件方法
</summary>
<typeparam name="TCom"></typeparam>
<param name="parameters">TCom 组件所需要的参数集合</param>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent.CreateComponent``1">
<summary>
创建自定义组件方法
</summary>
<typeparam name="TCom"></typeparam>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent.Render(System.Action{System.Object})">
<summary>
创建组件实例并渲染
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.JSModuleExtensions">
<summary>
JSModule extensions class
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.JSModuleExtensions.LoadModuleAsync(Microsoft.JSInterop.IJSRuntime,System.String,System.Boolean)">
<summary>
IJSRuntime 扩展方法 动态加载脚本
</summary>
<param name="jsRuntime"></param>
<param name="fileName"></param>
<param name="relative">是否为相对路径 默认 true</param>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.JSRuntimeExtensions">
<summary>
JSRuntime 扩展操作类
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.JSRuntimeExtensions.InternalInvokeAsync``1(Microsoft.JSInterop.IJSRuntime,System.String,System.Threading.CancellationToken,System.Object[])">
<summary>
调用 JSInvoke 方法
</summary>
<param name="jsRuntime">IJSRuntime 实例</param>
<param name="func">Javascript 方法</param>
<param name="token">取消传播</param>
<param name="args">Javascript 参数</param>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.JSRuntimeExtensions.InternalInvokeVoidAsync(Microsoft.JSInterop.IJSRuntime,System.String,System.Threading.CancellationToken,System.Object[])">
<summary>
调用 JSInvoke 方法
</summary>
<param name="jsRuntime">IJSRuntime 实例</param>
<param name="func">Javascript 方法</param>
<param name="token">取消传播</param>
<param name="args">Javascript 参数</param>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.InitTimezone">
<summary>
获取Web客户端时差
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.InitTimezone.TimezoneOffset">
<summary>
当前的客户端时差
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.InitTimezone.#ctor(Microsoft.JSInterop.IJSRuntime,BlazorComponent.I18n.CookieStorage,Microsoft.AspNetCore.Http.IHttpContextAccessor)">
<summary>
构造函数
</summary>
<param name="jsRuntime"></param>
<param name="storage"></param>
<param name="httpContextAccessor"></param>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.InitTimezone.SetTimezoneOffsetAsync">
<summary>
获取Web客户端时差
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.InitTimezone.Dispose">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.Startup">
<summary>
AppStartup启动类
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.Startup.ConfigureServices(Microsoft.Extensions.DependencyInjection.IServiceCollection)">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.UserResoures">
<summary>
当前用户资源
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.#ctor(BlazorComponent.I18n.CookieStorage,Masa.Blazor.MasaBlazor,Microsoft.AspNetCore.Http.IHttpContextAccessor)">
<summary>
构造函数
</summary>
<param name="cookieStorage"></param>
<param name="masaBlazor"></param>
<param name="httpContextAccessor"></param>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.CurrentUser">
<summary>
当前用户
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.IsDark">
<summary>
是否深色主图
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.Menus">
<summary>
当前菜单
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.PageTabItems">
<summary>
当前的Tab列表
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.SameLevelMenus">
<summary>
当前的菜单列表
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.AllSameLevelMenuSpas">
<summary>
当前的菜单与单页列表
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.WorkbenchOutputs">
<summary>
当前工作台
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.Dispose">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.InitAllAsync">
<summary>
初始化获取全部资源
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.InitMenuAsync">
<summary>
初始化获取当前菜单资源
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.InitUserAsync">
<summary>
初始化获取当前用户
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.IsHasButtonWithRole(System.String)">
<summary>
是否拥有按钮授权
</summary>
<param name="code"></param>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.IsHasPageWithRole(System.String)">
<summary>
是否拥有页面授权
</summary>
<param name="code"></param>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.SetMasaTheme(System.Nullable{System.Boolean})">
<summary>
设置深浅主题统一由这个方法为入口
</summary>
</member>
<member name="T:BlazorComponent.I18n.CookieStorage">
<summary>
CookieStorage
</summary>
</member>
<member name="M:BlazorComponent.I18n.CookieStorage.#ctor(Microsoft.JSInterop.IJSRuntime)">
<summary>
CookieStorage
</summary>
<param name="jsRuntime"></param>
</member>
<member name="M:BlazorComponent.I18n.CookieStorage.GetCookieAsync(System.String)">
<summary>
GetCookieAsync
</summary>
<param name="key"></param>
<returns></returns>
</member>
<member name="M:BlazorComponent.I18n.CookieStorage.GetCookie(System.String)">
<summary>
GetCookie
</summary>
<param name="key"></param>
<returns></returns>
</member>
<member name="M:BlazorComponent.I18n.CookieStorage.SetItemAsync``1(System.String,``0)">
<summary>
SetItemAsync
</summary>
<typeparam name="T"></typeparam>
<param name="key"></param>
<param name="value"></param>
</member>
</members>
</doc>

View File

@@ -1,84 +0,0 @@
/*自定义样式*/
.table-text-truncate {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
max-width: 200px;
min-width: 130px;
}
.table-minwidth {
min-width: 130px;
}
/*masa样式覆盖修改*/
.m-application--is-ltr .m-data-table__mobile-row__cell {
text-align: right;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
max-width: 200px;
}
.m-application--is-ltr .m-list-group--no-action > .m-list-group__items > .m-list-item {
padding-left: 36px;
}
.m-application--is-ltr .m-list--dense.m-list--nav .m-list-group--no-action > .m-list-group__items > .m-list-item {
padding-left: 36px;
}
.m-application--is-ltr .m-list-item__action:first-child, .m-application--is-ltr .m-list-item__icon:first-child {
margin-right: 8px;
}
.m-tabs-bar {
height: auto;
}
.m-breadcrumbs li:nth-child(even) {
padding: 0 6px;
}
.m-page-tabs > .m-tabs-bar {
border-bottom-left-radius: 20px !important;
border-bottom-right-radius: 20px !important;
height: 36px;
padding: 0 16px;
}
.m-application .text-start {
text-align: start !important;
min-width: 130px;
}
.neutral--text {
color: #1B2559 !important;
caret-color: #1B2559 !important;
}
/*下面都是html默认样式修改*/
html {
overflow-y: hidden
}
/*滚动条样式*/
::-webkit-scrollbar-track {
border-radius: 10px;
margin: 12px 0 0 0;
background-color: #F6F8FD;
}
::-webkit-scrollbar {
width: 4px;
height: 6px;
margin: 5px 0;
}
::-webkit-scrollbar-thumb {
margin: 5px 0;
border-radius: 4px;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.44, #A3AED0), color-stop(0.72, #A3AED0), color-stop(0.86, #A3AED0));
}

File diff suppressed because one or more lines are too long

View File

@@ -1,71 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Mapster;
using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Blazor;
/// <summary>
/// 操作日志
/// </summary>
public partial class Oplog
{
private readonly OperateLogPageInput search = new();
private IAppDataTable _datatable;
private List<StringFilters> CategoryFilters { get; set; } = new();
private List<StringFilters> ExeStatus { get; set; } = new();
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
CategoryFilters.Add(new StringFilters() { Key = "操作", Value = LogConst.LOG_OPERATE });
CategoryFilters.Add(new StringFilters() { Key = "第三方操作", Value = LogConst.LOG_OPENAPIOPERATE });
ExeStatus.Add(new StringFilters() { Key = "成功", Value = LogConst.LOG_SUCCESS });
ExeStatus.Add(new StringFilters() { Key = "失败", Value = LogConst.LOG_FAIL });
}
private async Task ClearClick()
{
var confirm = await PopupService.OpenConfirmDialogAsync("删除", "确定 ?");
if (confirm)
{
await App.GetService<OperateLogService>().DeleteAsync(CategoryFilters.Select(it => it.Value).ToArray());
await _datatable?.QueryClickAsync();
}
}
private Task<SqlSugarPagedList<SysOperateLog>> QueryCallAsync(OperateLogPageInput input)
{
input.Account = search.Account;
input.Category = search.Category;
input.ExeStatus = search.ExeStatus;
return App.GetService<OperateLogService>().PageAsync(input);
}
[Inject]
AjaxService AjaxService { get; set; }
async Task DownExportAsync(OperateLogPageInput input = null)
{
try
{
await AjaxService.DownFileAsync("file/operateLog", SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat(), input.Adapt<OperateLogInput>());
}
finally
{
}
}
}

View File

@@ -1,204 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/admin/role"
@using System.Linq.Expressions;
@using BlazorComponent;
@using Masa.Blazor.Presets;
@using Microsoft.AspNetCore.Authorization;
@using ThingsGateway.Admin.Application;
@namespace ThingsGateway.Admin.Blazor
@attribute [Authorize]
@inject UserResoures UserResoures
@inherits BaseComponentBase
@layout MainLayout
<AppDataTable @ref="_datatable" TItem="SysRole" SearchItem="RolePageInput" AddItem="RoleAddInput" EditItem="RoleEditInput"
SearchModel="@search" IsShowSearchKey
QueryCallAsync="QueryCallAsync" AddCallAsync="AddCallAsync"
EditCallAsync="EditCallAsync" DeleteCallAsync="DeleteCallAsync"
IsShowQueryButton
IsShowAddButton=@UserResoures.IsHasButtonWithRole("sysroleadd")
IsShowDeleteButton=@UserResoures.IsHasButtonWithRole("sysroledelete")
IsShowEditButton=@UserResoures.IsHasButtonWithRole("sysroleedit")>
<AddTemplate>
@GetRenderFragment(context)
</AddTemplate>
<EditTemplate>
@GetRenderFragment(context)
</EditTemplate>
<ItemColOperTemplate>
<MList Dense>
@if (@UserResoures.IsHasButtonWithRole("sysroleperresuorce"))
{
<MListItem OnClick="async()=>
{
ChoiceRoleId=context.Item.Id;
await ResuorceInitAsync();
IsShowResuorces=true;
}">
<MListItemTitle Class="ml-2">资源权限</MListItemTitle>
</MListItem>
}
@if (@UserResoures.IsHasButtonWithRole("sysroleperuser"))
{
<MListItem OnClick="async()=>
{
ChoiceRoleId=context.Item.Id;
await UserInitAsync();
IsShowUsers=true;
}">
<MListItemTitle Class="ml-2">授权用户</MListItemTitle>
</MListItem>
}
</MList>
</ItemColOperTemplate>
</AppDataTable>
<PDrawer @bind-Value="IsShowResuorces" OnCancel="() => IsShowResuorces = false"
Title=资源授权
Width=@(IsMobile?"100%":"600")
MaxWidth="600" OnSave="OnRoleHasResuorcesSaveAsync">
@if (IsShowResuorces)
{
<MSheet Outlined Class="ma-0 pa-2">
<MRow Align="AlignTypes.Center">
<MCol> <MLabel Class="ml-4 font-weight-black">菜单</MLabel> </MCol>
<MDivider Vertical />
<MCol> <MLabel Class="ml-4 font-weight-black">按钮</MLabel> </MCol>
</MRow>
</MSheet>
@foreach (var menu in ResTreeSelectors)
{
<MSheet Outlined Class="ma-0 pa-4">
<MRow Align="AlignTypes.Center">
<MCol>
<MListItem IsActive=@(RoleHasResuorces.Any(it=>it.MenuId==menu.Id))>
<ItemContent>
<MListItemContent>
<MListItemTitle>@menu.Title</MListItemTitle>
</MListItemContent>
<MListItemAction>
<MCheckbox TValue=bool Value="@context.Active" ValueChanged=@(enable=>
{
if(!enable)
RoleHasResuorces.RemoveWhere(it=>it.MenuId==menu.Id);
else if(!RoleHasResuorces.Any(it=>it.MenuId==menu.Id))
RoleHasResuorces.Add(new() {MenuId=menu.Id});
}
)></MCheckbox>
</MListItemAction>
</ItemContent>
</MListItem>
</MCol>
<MDivider Vertical />
<MCol>
@GetButtonCore(menu)
</MCol>
</MRow>
</MSheet>
}
}
</PDrawer>
<PDrawer @bind-Value="IsShowUsers" OnCancel="() => IsShowUsers = false"
Title=授权用户
Width=@(IsMobile?"100%":"500")
MaxWidth="500" OnSave="OnUsersSaveAsync">
<MCard Flat Class="ma-0 pa-4">
<MCardTitle Class="py-2">
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 ml-6" @bind-Value="SearchKey"
Outlined Label=@typeof(SysUser).GetDescription(nameof(SysUser.Account)) />
</MCardTitle>
<MTreeview Class="my-1" Dense OpenAll TItem="UserSelectorOutput" TKey="UserSelectorOutput" Selectable @bind-Value="UsersChoice"
Items="AllUsers" ItemText="r=>r.Account" ItemChildren="r=>null"
ItemKey=@(r=>r)>
<LabelContent>
<span title=@context.Item.Account>
@context.Item.Account
</span>
</LabelContent>
</MTreeview>
</MCard>
</PDrawer>
@code {
RenderFragment GetButtonCore(RoleGrantResourceMenu menu)
{
RenderFragment ViewSubMenu = null;
foreach (var button in menu.Button ?? new())
{
ViewSubMenu +=
@<MListItem Class="ml-6" IsActive=@(RoleHasResuorces.FirstOrDefault(it=>it.MenuId==menu.Id)?.ButtonInfo?.Contains(button.Id)==true)>
<ItemContent>
<MListItemContent>
<MListItemTitle>@button.Title</MListItemTitle>
</MListItemContent>
<MListItemAction>
<MCheckbox TValue=bool Value="@context.Active" ValueChanged=@(a=>
{
if(!a)
{
RoleHasResuorces.FirstOrDefault(it=>it.MenuId==menu.Id)?.ButtonInfo?.RemoveWhere(it=>it==button.Id);
}
else
{
if( !(RoleHasResuorces.FirstOrDefault(it=>it.MenuId==menu.Id)?.ButtonInfo?.Any(it=>it==button.Id)==true))
{
if(!RoleHasResuorces.Any(it=>it.MenuId==menu.Id))
{
RoleHasResuorces.Add(new() {MenuId=menu.Id});
}
RoleHasResuorces.FirstOrDefault(it=>it.MenuId==menu.Id).ButtonInfo.Add(button.Id);
}
}
})></MCheckbox>
</MListItemAction>
</ItemContent>
</MListItem>
;
}
return ViewSubMenu;
}
RenderFragment GetRenderFragment(RoleAddInput context)
{
RenderFragment renderFragment =
@<div>
<MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.Name)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Name />
<MSubheader Class="mt-4 mb-5 font-weight-black">@(context.Description(x => x.SortCode)) </MSubheader>
<MSlider @bind-Value=@context.SortCode Class="mb-5" TValue=int ThumbLabel="@("always")" Dense />
</div>
;
return renderFragment;
}
}

View File

@@ -1,114 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Masa.Blazor;
using Masa.Blazor.Presets;
using SqlSugar;
using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Blazor;
/// <summary>
/// 角色页面
/// </summary>
public partial class Role
{
private readonly RolePageInput search = new();
private IAppDataTable _datatable;
private List<UserSelectorOutput> AllUsers;
long ChoiceRoleId;
bool IsShowResuorces;
bool IsShowUsers;
List<RoleGrantResourceMenu> ResTreeSelectors = new();
List<RelationRoleResuorce> RoleHasResuorces = new();
private List<UserSelectorOutput> UsersChoice;
[CascadingParameter]
MainLayout MainLayout { get; set; }
private string SearchKey { get; set; }
private Task AddCallAsync(RoleAddInput input)
{
return App.GetService<RoleService>().AddAsync(input);
}
private async Task DeleteCallAsync(IEnumerable<SysRole> sysRoles)
{
await App.GetService<RoleService>().DeleteAsync(sysRoles.Select(a => a.Id).ToArray());
await MainLayout.StateHasChangedAsync();
}
private async Task EditCallAsync(RoleEditInput input)
{
await App.GetService<RoleService>().EditAsync(input);
await MainLayout.StateHasChangedAsync();
}
private async Task OnRoleHasResuorcesSaveAsync(ModalActionEventArgs args)
{
try
{
GrantResourceInput userGrantRoleInput = new();
var data = new List<SysResource>();
userGrantRoleInput.Id = ChoiceRoleId;
userGrantRoleInput.GrantInfoList = RoleHasResuorces;
await App.GetService<RoleService>().GrantResourceAsync(userGrantRoleInput);
IsShowResuorces = false;
}
catch (Exception ex)
{
args.Cancel();
await PopupService.EnqueueSnackbarAsync(ex, false);
}
await MainLayout.StateHasChangedAsync();
}
private async Task OnUsersSaveAsync(ModalActionEventArgs args)
{
try
{
GrantUserInput userGrantRoleInput = new();
userGrantRoleInput.Id = ChoiceRoleId;
userGrantRoleInput.GrantInfoList = UsersChoice.Select(it => it.Id).ToList();
await App.GetService<RoleService>().GrantUserAsync(userGrantRoleInput);
IsShowUsers = false;
}
catch (Exception ex)
{
args.Cancel();
await PopupService.EnqueueSnackbarAsync(ex, false);
}
await MainLayout.StateHasChangedAsync();
}
private Task<SqlSugarPagedList<SysRole>> QueryCallAsync(RolePageInput input)
{
return App.GetService<RoleService>().PageAsync(input);
}
private async Task ResuorceInitAsync()
{
ResTreeSelectors = (await App.GetService<ResourceService>().GetRoleGrantResourceMenusAsync());
RoleHasResuorces = (await App.GetService<RoleService>().OwnResourceAsync(ChoiceRoleId))?.GrantInfoList;
}
private async Task<List<UserSelectorOutput>> UserInitAsync()
{
AllUsers = await App.GetService<SysUserService>().UserSelectorAsync(SearchKey);
var data = await App.GetService<RoleService>().OwnUserAsync(ChoiceRoleId);
UsersChoice = AllUsers.Where(a => data.Contains(a.Id)).ToList();
return AllUsers;
}
}

View File

@@ -1,104 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Masa.Blazor;
using Masa.Blazor.Presets;
using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Blazor;
/// <summary>
/// 用户界面
/// </summary>
public partial class User
{
private readonly UserPageInput search = new();
private IAppDataTable _datatable;
private List<SysRole> AllRoles;
long ChoiceUserId;
bool IsShowRoles;
List<SysRole> RolesChoice = new();
string SearchName;
[CascadingParameter]
MainLayout MainLayout { get; set; }
private Task AddCallAsync(UserAddInput input)
{
return App.GetService<SysUserService>().AddAsync(input);
}
private async Task DeleteCallAsync(IEnumerable<SysUser> users)
{
await App.GetService<SysUserService>().DeleteAsync(users.Select(a => a.Id).ToArray());
await MainLayout.StateHasChangedAsync();
}
private async Task EditCallAsync(UserEditInput users)
{
await App.GetService<SysUserService>().EditAsync(users);
await MainLayout.StateHasChangedAsync();
}
private async Task OnRolesSaveAsync(ModalActionEventArgs args)
{
try
{
UserGrantRoleInput userGrantRoleInput = new();
userGrantRoleInput.Id = ChoiceUserId;
userGrantRoleInput.RoleIdList = RolesChoice.Select(it => it.Id).ToList();
await App.GetService<SysUserService>().GrantRoleAsync(userGrantRoleInput);
IsShowRoles = false;
}
catch (Exception ex)
{
args.Cancel();
await PopupService.EnqueueSnackbarAsync(ex, false);
}
await MainLayout.StateHasChangedAsync();
}
private Task<SqlSugarPagedList<SysUser>> QueryCallAsync(UserPageInput input)
{
return App.GetService<SysUserService>().PageAsync(input);
}
private async Task ResetPasswordAsync(SysUser sysUser)
{
await App.GetService<SysUserService>().ResetPasswordAsync(sysUser.Id);
await PopupService.EnqueueSnackbarAsync(new("成功", AlertTypes.Success));
await MainLayout.StateHasChangedAsync();
}
private async Task RoleInitAsync()
{
AllRoles = await App.GetService<RoleService>().RoleSelectorAsync();
var data = await App.GetService<RoleService>().GetRoleIdListByUserIdAsync(ChoiceUserId);
RolesChoice = AllRoles.Where(a => data.Contains(a.Id)).ToList();
}
private async Task UserStatusChangeAsync(SysUser context, bool enable)
{
try
{
if (enable)
await App.GetService<SysUserService>().EnableUserAsync(context.Id);
else
await App.GetService<SysUserService>().DisableUserAsync(context.Id);
}
finally
{
await _datatable?.QueryClickAsync();
await MainLayout.StateHasChangedAsync();
}
}
}

View File

@@ -1,102 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/login"
@layout BaseLayout
@inherits BaseComponentBase
@namespace ThingsGateway.Admin.Blazor
@using BlazorComponent;
@using Masa.Blazor.Presets;
<Ajax></Ajax>
@if (IsMobile)
{
<MCard @onkeydown=Enter Height=@("100%") >
@GetLoginCore()
</MCard>
}
else
{
<MRow NoGutters Style="height:100%">
<MCol Md=5 Sm=12>
<MSheet Elevation=1 Style="width:100%; height:100%;" Class="d-flex align-start flex-column mb-6">
<div class="d-flex align-center ml-12 mt-12">
<MAvatar Size="40" Color="teal">
<span class="white--text text-h6">@CONFIG_TITLE?.GetNameLen2()</span>
</MAvatar>
<h1>@CONFIG_TITLE</h1>
</div>
<div class="d-flex align-center ml-12 mt-12 mb-auto">
<h3>@CONFIG_REMARK</h3>
</div>
<div class="d-flex align-center pa-2" style="width:100%;height:100%;">
<MImage Src=@(BlazorResourceConst.ResourceUrl+"images/login-left.svg")></MImage>
</div>
</MSheet>
</MCol>
<MCol Md=7 Sm=12 Align="AlignTypes.Center">
<MRow Md=6 Sm=12 Justify="JustifyTypes.Center" Align="AlignTypes.Center">
<MCard Class="px-16 py-12" @onkeydown=Enter>
@GetLoginCore()
</MCard>
</MRow>
</MCol>
</MRow>
}
@code {
RenderFragment GetLoginCore()
{
RenderFragment ViewSubMenu =
@<div class="mt-2 px-2 py-1 mx-auto text-center my-auto" >
<MAvatar Size=80>
<MImage Src=@UserLogoUrl>
</MImage>
</MAvatar>
<h5 class="mt-2 mb-12">@Welcome 👋</h5>
<MTextField TValue="string"
Label=账号
Outlined
HideDetails="@("auto")"
@bind-Value=@loginModel.Account>
</MTextField>
<MTextField TValue="string"
Class="mt-10"
Label="密码"
Type="@(_showPassword ? "text" : "password")"
AppendIcon="@(_showPassword ? "mdi-eye" : "mdi-eye-off")"
OnAppendClick="()=>_showPassword = !_showPassword"
Outlined
HideDetails="@("auto")"
@bind-Value=@Password>
</MTextField>
@if (_showCaptcha)
{
<PImageCaptcha @ref=captcha @bind-Value="CaptchaValue"
TextFieldClass="mt-10 mx-auto"
Height="60"
Label=验证码 Outlined
OnRefresh="RefreshCode"
ErrorMessage=验证码错误>
</PImageCaptcha>
}
<MButton Class="mt-11 rounded-4" OnClick=LoginAsync Height=46 Width=@("100%") Color="primary">登录</MButton>
</div>
;
return ViewSubMenu;
}
}

View File

@@ -1,143 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DataEncryption;
using Masa.Blazor.Presets;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.Hosting;
using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Admin.Core;
using ThingsGateway.Admin.Core.JsonExtensions;
namespace ThingsGateway.Admin.Blazor;
/// <summary>
/// 登录页面
/// </summary>
public partial class Login
{
private string CaptchaValue;
bool _showPassword;
bool _showCaptcha;
private readonly LoginInput loginModel = new();
[Inject]
AjaxService AjaxService { get; set; }
string UserLogoUrl { get; set; } = BlazorResourceConst.ResourceUrl + "images/defaultUser.svg";
string Welcome { get; set; }
private ValidCodeOutput CaptchaInfo { get; set; }
private string Password { get; set; }
private string CONFIG_REMARK { get; set; }
private string CONFIG_TITLE { get; set; }
private async Task Enter(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
await LoginAsync();
}
}
private PImageCaptcha captcha;
private async Task LoginAsync()
{
loginModel.ValidCodeReqNo = CaptchaInfo.ValidCodeReqNo;
loginModel.ValidCode = CaptchaValue;
loginModel.Password = DESCEncryption.Encrypt(Password, DESCKeyConst.DESCKey);
if (IsMobile)
{
loginModel.Device = AuthDeviceTypeEnum.APP;
}
else
{
loginModel.Device = AuthDeviceTypeEnum.PC;
}
var ajaxOption = new AjaxOption { Url = "/auth/b/login", Data = loginModel, };
var str = await AjaxService.GetMessageAsync(ajaxOption);
if (str != null)
{
var ret = str.ToJsonWithT<UnifyResult<LoginOutput>>();
if (ret.Code != 200)
{
if (captcha != null)
{
await captcha.RefreshCode();
}
await PopupService.EnqueueSnackbarAsync(new("登录错误" + ": " + ret.Msg.ToString(), AlertTypes.Error));
}
else
{
await PopupService.EnqueueSnackbarAsync(new("登录成功", AlertTypes.Success));
await Task.Delay(500);
var userId = await App.GetService<SysUserService>().GetIdByAccountAsync(loginModel.Account);
var data = await App.GetService<UserCenterService>().GetLoginDefaultRazorAsync(userId);
var sameLevelMenus = await App.GetService<ResourceService>().GetaMenuAndSpaListAsync();
if (NavigationManager.ToAbsoluteUri(NavigationManager.Uri).AbsolutePath == "/Login" || NavigationManager.ToAbsoluteUri(NavigationManager.Uri).AbsolutePath == "/")
await AjaxService.GotoAsync(sameLevelMenus.FirstOrDefault(a => a.Id == data)?.Component ?? "index");
else
await AjaxService.GotoAsync(NavigationManager.Uri);
}
}
else
{
if (captcha != null)
{
await captcha.RefreshCode();
}
await PopupService.EnqueueSnackbarAsync(new("登录错误", AlertTypes.Error));
}
}
[Inject]
private NavigationManager NavigationManager { get; set; }
/// <inheritdoc/>
protected override async Task OnParametersSetAsync()
{
if (App.HostEnvironment.IsDevelopment())
{
loginModel.Account = "superAdmin";
Password = "111111";
}
GetCaptchaInfo();
CONFIG_TITLE = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_TITLE))?.ConfigValue;
CONFIG_REMARK = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_REMARK))?.ConfigValue;
_showCaptcha = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_CAPTCHA_OPEN))?.ConfigValue?.ToBoolean() == true;
Welcome = "欢迎使用" + CONFIG_TITLE + "!";
await base.OnParametersSetAsync();
}
private void GetCaptchaInfo()
{
CaptchaInfo = App.GetService<AuthService>().GetCaptchaInfo();
}
private Task<string> RefreshCode()
{
CaptchaInfo = App.GetService<AuthService>().GetCaptchaInfo();
return Task.FromResult(CaptchaInfo.CodeValue);
}
}

View File

@@ -1,74 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Blazor.Core;
namespace ThingsGateway.Admin.Blazor;
/// <summary>
/// Layout
/// </summary>
public partial class MainLayout
{
private static readonly string[] selfPatterns =
{
};
bool Changed { get; set; }
private bool? _drawerOpen = true;
private PageTabs _pageTabs;
private string CONFIG_COPYRIGHT = "";
private string CONFIG_COPYRIGHT_URL = "";
private string CONFIG_TITLE = "";
/// <summary>
/// IsMobile
/// </summary>
[CascadingParameter(Name = "IsMobile")]
public bool IsMobile { get; set; }
private List<NavItem> Navs { get; set; } = new();
[Inject]
private UserResoures UserResoures { get; set; }
/// <summary>
/// 页面刷新
/// </summary>
/// <returns></returns>
public async Task StateHasChangedAsync()
{
CONFIG_COPYRIGHT = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_COPYRIGHT)).ConfigValue;
CONFIG_TITLE = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_TITLE)).ConfigValue;
CONFIG_COPYRIGHT_URL = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_COPYRIGHT_URL)).ConfigValue;
await UserResoures.InitUserAsync();
await UserResoures.InitMenuAsync();
Navs = UserResoures.Menus.Parse();
Changed = !Changed;
await InvokeAsync(StateHasChanged);
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{
await StateHasChangedAsync();
await base.OnInitializedAsync();
}
}

View File

@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
</PropertyGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Admin.Blazor.Core\ThingsGateway.Admin.Blazor.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,363 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>ThingsGateway.Admin.Blazor</name>
</assembly>
<members>
<member name="T:ThingsGateway.Admin.Blazor.Core.AppDataTable`4">
<summary>
通用表格
</summary>
<typeparam name="TItem"></typeparam>
<typeparam name="SearchItem"></typeparam>
<typeparam name="AddItem"></typeparam>
<typeparam name="EditItem"></typeparam>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.AddCallAsync">
<summary>
添加项委托
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.AddTemplate">
<summary>
获得/设置 添加模板
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.ClassString">
<summary>
MSheet.Class
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.StyleString">
<summary>
MSheet.Style
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.DeleteCallAsync">
<summary>
删除项委托
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.Dense">
<summary>
表格紧凑
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.EditCallAsync">
<summary>
编辑项委托
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.EditTemplate">
<summary>
获得/设置 编辑模板
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.Detailemplate">
<summary>
获得/设置 详情模板
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.FilterHeaders">
<summary>
表头过滤返回DataTableHeader列表传输参数已包含全部初始表头与表头标题
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.Filters">
<summary>
表头过滤之后执行的方法返回Filter值ture则显示false则隐藏
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.HeaderTemplate">
<summary>
获得/设置 Table Header 模板
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsMenuOperTemplate">
<summary>
右侧操作栏以菜单形式显示
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsPage">
<summary>
是否分页
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowAddButton">
<summary>
是否显示添加按钮
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowClearSearch">
<summary>
是否显示清空搜索
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowDeleteButton">
<summary>
是否显示删除按钮
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowDetailButton">
<summary>
是否显示详情按钮
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowEditButton">
<summary>
是否显示编辑按钮
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowFilter">
<summary>
是否显示过滤
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowOperCol">
<summary>
是否显示右侧操作栏
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowQueryButton">
<summary>
是否显示查询按钮
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowSearchKey">
<summary>
是否显示搜索关键字
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowSelect">
<summary>
是否显示表格多项选择
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowToolbar">
<summary>
是否显示顶部操作工具栏
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.ItemColOperTemplate">
<summary>
获得/设置 Table Oper 模板
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.ItemColTemplate">
<summary>
获得/设置 Table Cols 模板
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.ItemColWithDTTemplate">
<summary>
独立设置 Table Cols 模板需自行实现DateTime类型的时区转换
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.Items">
<summary>
当前显示项目
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.OtherToolbarTemplate">
<summary>
获得/设置 其他操作栏模板
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.PageSizeItems">
<summary>
分页选择项目
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.QueryCallAsync">
<summary>
查询项委托
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.SearchModel">
<summary>
获得/设置 SearchModel 实例
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.SearchTemplate">
<summary>
获得/设置 查询与操作栏模板
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.QueryClickAsync">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.OnAfterRenderAsync(System.Boolean)">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.OnInitializedAsync">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Core.IAppDataTable">
<summary>
通用表格
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Core.IAppDataTable.QueryClickAsync">
<summary>
查询刷新
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Blazor.StringFilters">
<summary>
键值表示
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.StringFilters.Key">
<summary>
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.StringFilters.Value">
<summary>
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Base">
<summary>
Base
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Base.OnAfterRenderAsync(System.Boolean)">
<summary>
<inheritdoc/>
</summary>
<param name="firstRender"></param>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Config">
<summary>
系统配置页面
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Config.OnParametersSetAsync">
<summary>
<inheritdoc/>
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Index">
<summary>
首页
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Index.OnParametersSetAsync">
<summary>
<inheritdoc/>
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Menu">
<summary>
菜单页面
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Menu.OnParametersSetAsync">
<summary>
<inheritdoc/>
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.Blazor.OpenApiSession">
<summary>
OpenApiSession
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.OpenApiUserR">
<summary>
OpenApiUserR
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Oplog">
<summary>
操作日志
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Oplog.OnInitialized">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Role">
<summary>
角色页面
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Session">
<summary>
会话页面
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Spa">
<summary>
SPA
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.User">
<summary>
用户界面
</summary>
</member>
<member name="T:ThingsGateway.Admin.Blazor.UserCenter">
<summary>
个人设置
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.UserCenter.OnParametersSetAsync">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Vislog">
<summary>
访问日志页面
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Vislog.CategoryFilters">
<summary>
日志分类菜单
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.Vislog.ExeStatus">
<summary>
执行结果菜单
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Vislog.OnInitialized">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.Login">
<summary>
登录页面
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.Login.OnParametersSetAsync">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Admin.Blazor.MainLayout">
<summary>
Layout
</summary>
</member>
<member name="P:ThingsGateway.Admin.Blazor.MainLayout.IsMobile">
<summary>
IsMobile
</summary>
</member>
<member name="M:ThingsGateway.Admin.Blazor.MainLayout.StateHasChangedAsync">
<summary>
页面刷新
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.Blazor.MainLayout.OnInitializedAsync">
<summary>
<inheritdoc/>
</summary>
<returns></returns>
</member>
</members>
</doc>

View File

@@ -1,81 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Mapster;
using Microsoft.Extensions.Caching.Memory;
namespace ThingsGateway.Admin.Core;
/// <summary>
/// 系统内存缓存
/// </summary>
public class SysMemoryCache : IDisposable
{
private readonly MemoryCache _cache;
/// <summary>
/// <inheritdoc cref="SysMemoryCache"/>
/// </summary>
public SysMemoryCache()
{
_cache = new MemoryCache(new MemoryCacheOptions());
}
/// <inheritdoc/>
public T Get<T>(string key, bool mapster)
{
return _cache.TryGetValue<T>(key, out var value) ? mapster ? value.Adapt<T>() : value : default;
}
/// <inheritdoc/>
public async Task<T> GetOrCreateAsync<T>(string key, Func<ICacheEntry, Task<T>> func, bool mapster) where T : class
{
var value = await _cache.GetOrCreateAsync(key, func);
return mapster ? value.Adapt<T>() : value;
}
/// <inheritdoc/>
public T GetOrCreate<T>(string key, Func<ICacheEntry, T> func, bool mapster) where T : class
{
var value = _cache.GetOrCreate(key, func);
return mapster ? value.Adapt<T>() : value;
}
/// <inheritdoc/>
public void Remove(object key)
{
_cache.Remove(key);
}
/// <inheritdoc/>
public void Set<T>(object key, T value, bool mapster)
{
_cache.Set(key, mapster ? value.Adapt<T>() : value);
}
/// <inheritdoc/>
public void Set<T>(object key, T value, TimeSpan offset, bool mapster)
{
_cache.Set(key, mapster ? value.Adapt<T>() : value, offset);
}
/// <inheritdoc/>
public void Dispose()
{
_cache?.Dispose();
}
}

View File

@@ -1,95 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using SqlSugar;
using System.Collections;
using System.Data;
using System.Reflection;
using ThingsGateway.Admin.Core.JsonExtensions;
namespace ThingsGateway.Admin.Core;
/// <summary>
/// 对象拓展
/// </summary>
[SuppressSniffer]
public static class ListExtensions
{
/// <summary>
/// List转DataTable
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <returns></returns>
public static DataTable ToDataTable<T>(this List<T> list)
{
DataTable result = new();
if (list.Count > 0)
{
var propertys = list[0].GetType().GetPropertiesWithCache();
foreach (PropertyInfo pi in propertys)
{
Type colType = pi.PropertyType;
if (colType.IsGenericType && colType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
colType = colType.GetGenericArguments().First();
}
if (IsIgnoreColumn(pi))
continue;
if (IsJsonColumn(pi))//如果是json特性就是sting类型
colType = typeof(string);
if (colType.IsEnum)//如果是Enum需要转string才会保存Enum字符串
colType = typeof(string);
result.Columns.Add(pi.Name, colType);
}
for (int i = 0; i < list.Count; i++)
{
ArrayList tempList = new();
foreach (PropertyInfo pi in propertys)
{
if (IsIgnoreColumn(pi))
continue;
object obj = pi.GetValue(list[i], null);
if (IsJsonColumn(pi))//如果是json特性就是转化为json格式
obj = obj?.ToJsonString();//如果json字符串是空就传null
tempList.Add(obj);
}
object[] array = tempList.ToArray();
result.LoadDataRow(array, true);
}
}
return result;
}
/// <summary>
/// SqlSugar是否忽略字段
/// </summary>
/// <param name="pi"></param>
/// <returns></returns>
private static bool IsIgnoreColumn(PropertyInfo pi)
{
return pi.GetCustomAttribute<SugarColumn>(false).IsIgnore == true;
}
/// <summary>
/// SqlSugar是否Json字段
/// </summary>
/// <param name="pi"></param>
/// <returns></returns>
private static bool IsJsonColumn(PropertyInfo pi)
{
return pi.GetCustomAttribute<SugarColumn>(false).IsJson == true;
}
}

View File

@@ -1,154 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using System.Text.RegularExpressions;
namespace ThingsGateway.Admin.Core;
/// <summary>
/// 对象拓展
/// </summary>
[SuppressSniffer]
public static class StringExtensions
{
/// <summary>
/// 返回List,无其他处理
/// </summary>
public static List<string> StringToList(this string str)
{
return new List<string>() { str };
}
/// <summary>
/// 用 正则表达式 判断字符是不是汉字
/// </summary>
/// <param name="text">待判断字符或字符串</param>
/// <returns>真:是汉字;假:不是</returns>
private static bool IsChinese(string text) => Regex.IsMatch(text, @"[\u4e00-\u9fbb]");
/// <summary>
/// 获取字符串中的两个字符作为名称简述
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static string GetNameLen2(this string name)
{
if (name.IsNullOrEmpty())
return string.Empty;
var nameLength = name.Length;//获取姓名长度
string nameWritten = name;//需要绘制的文字
if (nameLength > 2)//如果名字长度超过2个
{
// 如果用户输入的姓名大于等于3个字符截取后面两位
string firstName = name.Substring(0, 1);
if (IsChinese(firstName))
{
// 截取倒数两位汉字
nameWritten = name.Substring(name.Length - 2);
}
else
{
// 截取第一个英文字母和第二个大写的字母
var data = Regex.Match(name, @"[A-Z]?[a-z]+([A-Z])").Value;
nameWritten = data.FirstCharToUpper() + data.LastCharToUpper();
if (nameWritten.IsNullOrEmpty())
{
nameWritten = name.FirstCharToUpper() + name.LastCharToUpper();
}
}
}
return nameWritten;
}
/// <summary>
/// 字符串是 null 或者 空
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsNullOrEmpty(this string value) => value == null || value!.Length <= 0;
/// <summary>
/// 返回字符串首字符的大写字母
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static string FirstCharToUpper(this string input) => input.IsNullOrEmpty() ? input : input.First().ToString().ToUpper();
/// <summary>
/// 返回字符串尾字符的大写字母
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static string LastCharToUpper(this string input) => input.IsNullOrEmpty() ? input : input.Last().ToString().ToUpper();
/// <summary>
/// 转换布尔值
/// </summary>
/// <returns></returns>
public static bool ToBoolean(this string value, bool defaultValue = false) => value?.ToUpper() switch
{
"1" or "TRUE" => true,
_ => defaultValue,
};
/// <summary>
/// ToLong
/// </summary>
/// <returns></returns>
public static long ToLong(this string value, long defaultValue = 0) => value.IsNullOrEmpty() ? defaultValue : Int64.TryParse(value, out var n) ? n : defaultValue;
/// <summary>
/// ToInt
/// </summary>
/// <returns></returns>
public static int ToInt(this string value, int defaultValue = 0) => value.IsNullOrEmpty() ? defaultValue : Int32.TryParse(value, out var n) ? n : defaultValue;
/// <summary>
/// ToDecimal
/// </summary>
/// <returns></returns>
public static decimal ToDecimal(this string value, int defaultValue = 0) => value.IsNullOrEmpty() ? defaultValue : Decimal.TryParse(value, out var n) ? n : defaultValue;
/// <summary>
/// 匹配手机号码
/// </summary>
/// <param name="s">源字符串</param>
/// <returns>是否匹配成功</returns>
public static bool MatchPhoneNumber(this string s) => !string.IsNullOrEmpty(s) && Regex.IsMatch(s, @"^1[3456789][0-9]{9}$");
/// <summary>
/// 匹配邮箱格式
/// </summary>
/// <param name="s">源字符串</param>
/// <returns>是否匹配成功</returns>
public static bool MatchEmail(this string s) => !string.IsNullOrEmpty(s) && Regex.IsMatch(s, @"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$");
/// <summary>合并多段路径</summary>
/// <param name="path"></param>
/// <param name="ps"></param>
/// <returns></returns>
public static string CombinePath(this string path, params string[] ps)
{
if (ps == null || ps.Length <= 0) return path;
path ??= string.Empty;
foreach (var item in ps)
{
if (!item.IsNullOrEmpty()) path = Path.Combine(path, item);
}
return path;
}
}

View File

@@ -1,183 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
namespace ThingsGateway.Admin.Core;
/// <summary>
/// 对象拓展
/// </summary>
[SuppressSniffer]
public static class SysDateTimeExtensions
{
private static readonly DateTime _dt1970 = new(1970, 1, 1);
/// <summary>
/// 系统默认使用的当前时间
/// </summary>
public static DateTime CurrentDateTime => DateTime.Now;
/// <summary>
/// 返回yyyy-MM-dd HH:mm:ss:fff zz时间格式字符串
/// </summary>
public static string ToDefaultDateTimeFormat(this in DateTime dt, TimeSpan offset)
{
if (dt.Kind == DateTimeKind.Utc)
return new DateTimeOffset(dt.ToLocalTime(), offset).ToString("yyyy-MM-dd HH:mm:ss:fff zz");
else if (dt == DateTime.MinValue || dt == DateTime.MaxValue)
return dt.ToString("yyyy-MM-dd HH:mm:ss:fff zz");
else
return new DateTimeOffset(dt, offset).ToString("yyyy-MM-dd HH:mm:ss:fff zz");
}
/// <summary>
/// 返回yyyy-MM-dd HH:mm:ss:fff zz时间格式字符串
/// </summary>
public static string ToDefaultDateTimeFormat(this in DateTime dt)
{
return dt.ToString("yyyy-MM-dd HH:mm:ss:fff zz");
}
/// <summary>
/// 返回yyyy-MM-dd HH:mm:ss:fff zz时间格式字符串
/// </summary>
public static string ToFileDateTimeFormat(this in DateTime dt)
{
return dt.ToString("yyyy-MM-dd HH-mm-ss-fff zz");
}
/// <summary>
/// 计算2个时间差返回文字描述
/// </summary>
/// <param name="beginTime">开始时间</param>
/// <param name="endTime">结束时间</param>
/// <returns>时间差</returns>
public static string GetDiffTime(this in DateTime beginTime, in DateTime endTime)
{
string strResout = string.Empty;
//获得2时间的时间间隔秒计算
TimeSpan span = endTime.Subtract(beginTime);
int sec = Convert.ToInt32(span.TotalSeconds);
int minutes = 1 * 60;
int hours = minutes * 60;
int day = hours * 24;
int month = day * 30;
int year = month * 12;
//提醒时间,到了返回1,否则返回0
if (sec > year)
{
strResout += (sec / year) + "年";
sec %= year; //剩余
}
if (sec > month)
{
strResout += (sec / month) + "月";
sec %= month;
}
if (sec > day)
{
strResout += (sec / day) + "天";
sec %= day;
}
if (sec > hours)
{
strResout += (sec / hours) + "小时";
sec %= hours;
}
if (sec > minutes)
{
strResout += (sec / minutes) + "分";
sec %= minutes;
}
strResout += sec + "秒";
return strResout;
}
/// <summary>
/// 计算2个时间差返回文字描述
/// </summary>
/// <param name="beginTime">开始时间</param>
/// <param name="endTime">结束时间</param>
/// <returns>时间差</returns>
public static string GetDiffTime(this in DateTimeOffset beginTime, in DateTimeOffset endTime)
{
string strResout = string.Empty;
//获得2时间的时间间隔秒计算
TimeSpan span = endTime.Subtract(beginTime);
int sec = Convert.ToInt32(span.TotalSeconds);
int minutes = 1 * 60;
int hours = minutes * 60;
int day = hours * 24;
int month = day * 30;
int year = month * 12;
//提醒时间,到了返回1,否则返回0
if (sec > year)
{
strResout += (sec / year) + "年";
sec %= year; //剩余
}
if (sec > month)
{
strResout += (sec / month) + "月";
sec %= month;
}
if (sec > day)
{
strResout += (sec / day) + "天";
sec %= day;
}
if (sec > hours)
{
strResout += (sec / hours) + "小时";
sec %= hours;
}
if (sec > minutes)
{
strResout += (sec / minutes) + "分";
sec %= minutes;
}
strResout += sec + "秒";
return strResout;
}
/// <summary>
/// ToLong
/// </summary>
/// <returns></returns>
public static long ToLong(this DateTime value, long defaultValue = 0)
{
// 特殊处理时间转Unix毫秒
if (value == DateTime.MinValue) return 0;
//// 先转UTC时间再相减以得到绝对时间差
//return (Int32)(dt.ToUniversalTime() - _dt1970).TotalSeconds;
return (Int64)(value - _dt1970).TotalMilliseconds;
}
}

View File

@@ -1,111 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Text;
namespace ThingsGateway.Admin.Core;
/// <summary>
/// 日志写入文件的组件
/// </summary>
public sealed class LoggingFileComponent : IServiceComponent
{
/// <inheritdoc/>
public void Load(IServiceCollection services, ComponentContext componentContext)
{
var logFileEnable = App.GetConfig<bool?>("Logging:LogEnable:File");
if (logFileEnable != true) return;
//获取默认日志等级
var defaultLevel = App.GetConfig<LogLevel?>("Logging:LogLevel:File");
//获取程序根目录
var rootPath = App.HostEnvironment.ContentRootPath;
if (defaultLevel != null)//如果默认日志等级不是空
{
//遍历日志等级
foreach (LogLevel level in Enum.GetValues(typeof(LogLevel)))
{
//如果日志等级是默认等级和最大等级之间
if (level >= defaultLevel && level != LogLevel.None)
{
//每天创建一个日志文件
services.AddLogging(builder =>
{
var fileName = "logs/" + level.ToString() + "/{0:yyyy}-{0:MM}-{0:dd}{0:zz}.log";
builder.AddFile(fileName, options =>
{
SetLogOptions(options, level);//日志格式化
});
});
}
}
}
else
{
//添加日志文件
services.AddFileLogging("logs/{0:yyyy}-{0:MM}-{0:dd}{0:zz}.log", options =>
{
SetLogOptions(options, null);//日志格式化
});
}
}
/// <summary>
/// 日志格式化
/// </summary>
/// <param name="options"></param>
/// <param name="logLevel"></param>
private static void SetLogOptions(FileLoggerOptions options, LogLevel? logLevel)
{
//每天创建一个日志文件
var rootPath = App.HostEnvironment.ContentRootPath;
if (logLevel != null)//如果日志等级不为空
{
//过滤日志等级
options.WriteFilter = (logMsg) =>
{
//不写入LoggingMonitor
if (logMsg.LogName == "System.Logging.LoggingMonitor")
return false;
//只写入NetCore日志
if (!logMsg.LogName.StartsWith("System") && !logMsg.LogName.StartsWith("Microsoft"))
return false;
return logMsg.LogLevel == logLevel;
};
}
//定义日志文件名
options.FileNameRule = fileName =>
{
return rootPath + "\\" + string.Format(fileName, SysDateTimeExtensions.CurrentDateTime);
};
options.FileSizeLimitBytes = 50 * 1024 * 1024;//日志最大50M
options.MessageFormat = logMsg =>
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine("【日志级别】:" + logMsg.LogLevel);
stringBuilder.AppendLine("【日志类名】:" + logMsg.LogName);
stringBuilder.AppendLine("【日志时间】:" + SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
stringBuilder.AppendLine("【日志内容】:" + logMsg.Message);
if (logMsg.Exception != null)
{
stringBuilder.AppendLine("【异常信息】:" + logMsg.Exception);
}
return stringBuilder.ToString();
};
}
}

View File

@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
</PropertyGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.8.8.43" />
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.8.8.43" />
<PackageReference Include="Furion.Pure" Version="4.8.8.43" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.105" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
<PackageReference Include="MiniExcel" Version="1.31.2" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -1,135 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33829.357
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Core", "ThingsGateway.Admin.Core\ThingsGateway.Admin.Core.csproj", "{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "script", "script", "{0BBD36E5-4FDB-4A9F-8387-6A39BEB6D87D}"
ProjectSection(SolutionItems) = preProject
..\.gitattributes = ..\.gitattributes
..\.gitignore = ..\.gitignore
Directory.Build.props = Directory.Build.props
..\LICENSE.txt = ..\LICENSE.txt
..\README.md = ..\README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Entry", "ThingsGateway.Web.Entry\ThingsGateway.Web.Entry.csproj", "{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Core", "ThingsGateway.Web.Core\ThingsGateway.Web.Core.csproj", "{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Blazor.Core", "ThingsGateway.Admin.Blazor.Core\ThingsGateway.Admin.Blazor.Core.csproj", "{096A0468-B4F0-490F-ABDF-78F66CCE2870}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Blazor", "ThingsGateway.Admin.Blazor\ThingsGateway.Admin.Blazor.csproj", "{6819B227-F69F-4478-819B-B95F170E11D5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Application", "ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj", "{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{07EEACCE-83E1-459F-89B8-C2DFD30AB86E}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.ApiController", "ThingsGateway.Admin.ApiController\ThingsGateway.Admin.ApiController.csproj", "{516316AA-8891-4741-8D30-9565CEB64FEA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x86 = Debug|x86
KINGVIEW|Any CPU = KINGVIEW|Any CPU
KINGVIEW|x86 = KINGVIEW|x86
Release|Any CPU = Release|Any CPU
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Debug|x86.ActiveCfg = Debug|Any CPU
{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Debug|x86.Build.0 = Debug|Any CPU
{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.KINGVIEW|Any CPU.ActiveCfg = Release|Any CPU
{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.KINGVIEW|Any CPU.Build.0 = Release|Any CPU
{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.KINGVIEW|x86.ActiveCfg = Release|Any CPU
{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.KINGVIEW|x86.Build.0 = Release|Any CPU
{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Release|Any CPU.Build.0 = Release|Any CPU
{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Release|x86.ActiveCfg = Release|Any CPU
{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Release|x86.Build.0 = Release|Any CPU
{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Debug|x86.ActiveCfg = Debug|x86
{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Debug|x86.Build.0 = Debug|x86
{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.KINGVIEW|Any CPU.ActiveCfg = KINGVIEW|Any CPU
{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.KINGVIEW|Any CPU.Build.0 = KINGVIEW|Any CPU
{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.KINGVIEW|x86.ActiveCfg = KINGVIEW|x86
{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.KINGVIEW|x86.Build.0 = KINGVIEW|x86
{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Release|Any CPU.Build.0 = Release|Any CPU
{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Release|x86.ActiveCfg = Release|x86
{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Release|x86.Build.0 = Release|x86
{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Debug|x86.ActiveCfg = Debug|Any CPU
{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Debug|x86.Build.0 = Debug|Any CPU
{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.KINGVIEW|Any CPU.ActiveCfg = Debug|Any CPU
{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.KINGVIEW|Any CPU.Build.0 = Debug|Any CPU
{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.KINGVIEW|x86.ActiveCfg = Debug|Any CPU
{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.KINGVIEW|x86.Build.0 = Debug|Any CPU
{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Release|Any CPU.Build.0 = Release|Any CPU
{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Release|x86.ActiveCfg = Release|Any CPU
{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Release|x86.Build.0 = Release|Any CPU
{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Debug|Any CPU.Build.0 = Debug|Any CPU
{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Debug|x86.ActiveCfg = Debug|Any CPU
{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Debug|x86.Build.0 = Debug|Any CPU
{096A0468-B4F0-490F-ABDF-78F66CCE2870}.KINGVIEW|Any CPU.ActiveCfg = Debug|Any CPU
{096A0468-B4F0-490F-ABDF-78F66CCE2870}.KINGVIEW|Any CPU.Build.0 = Debug|Any CPU
{096A0468-B4F0-490F-ABDF-78F66CCE2870}.KINGVIEW|x86.ActiveCfg = Debug|Any CPU
{096A0468-B4F0-490F-ABDF-78F66CCE2870}.KINGVIEW|x86.Build.0 = Debug|Any CPU
{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Release|Any CPU.ActiveCfg = Release|Any CPU
{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Release|Any CPU.Build.0 = Release|Any CPU
{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Release|x86.ActiveCfg = Release|Any CPU
{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Release|x86.Build.0 = Release|Any CPU
{6819B227-F69F-4478-819B-B95F170E11D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6819B227-F69F-4478-819B-B95F170E11D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6819B227-F69F-4478-819B-B95F170E11D5}.Debug|x86.ActiveCfg = Debug|Any CPU
{6819B227-F69F-4478-819B-B95F170E11D5}.Debug|x86.Build.0 = Debug|Any CPU
{6819B227-F69F-4478-819B-B95F170E11D5}.KINGVIEW|Any CPU.ActiveCfg = Debug|Any CPU
{6819B227-F69F-4478-819B-B95F170E11D5}.KINGVIEW|Any CPU.Build.0 = Debug|Any CPU
{6819B227-F69F-4478-819B-B95F170E11D5}.KINGVIEW|x86.ActiveCfg = Debug|Any CPU
{6819B227-F69F-4478-819B-B95F170E11D5}.KINGVIEW|x86.Build.0 = Debug|Any CPU
{6819B227-F69F-4478-819B-B95F170E11D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6819B227-F69F-4478-819B-B95F170E11D5}.Release|Any CPU.Build.0 = Release|Any CPU
{6819B227-F69F-4478-819B-B95F170E11D5}.Release|x86.ActiveCfg = Release|Any CPU
{6819B227-F69F-4478-819B-B95F170E11D5}.Release|x86.Build.0 = Release|Any CPU
{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Debug|x86.ActiveCfg = Debug|Any CPU
{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Debug|x86.Build.0 = Debug|Any CPU
{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.KINGVIEW|Any CPU.ActiveCfg = Debug|Any CPU
{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.KINGVIEW|Any CPU.Build.0 = Debug|Any CPU
{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.KINGVIEW|x86.ActiveCfg = Debug|Any CPU
{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.KINGVIEW|x86.Build.0 = Debug|Any CPU
{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Release|Any CPU.Build.0 = Release|Any CPU
{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Release|x86.ActiveCfg = Release|Any CPU
{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Release|x86.Build.0 = Release|Any CPU
{516316AA-8891-4741-8D30-9565CEB64FEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{516316AA-8891-4741-8D30-9565CEB64FEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{516316AA-8891-4741-8D30-9565CEB64FEA}.Debug|x86.ActiveCfg = Debug|Any CPU
{516316AA-8891-4741-8D30-9565CEB64FEA}.Debug|x86.Build.0 = Debug|Any CPU
{516316AA-8891-4741-8D30-9565CEB64FEA}.KINGVIEW|Any CPU.ActiveCfg = Debug|Any CPU
{516316AA-8891-4741-8D30-9565CEB64FEA}.KINGVIEW|Any CPU.Build.0 = Debug|Any CPU
{516316AA-8891-4741-8D30-9565CEB64FEA}.KINGVIEW|x86.ActiveCfg = Debug|Any CPU
{516316AA-8891-4741-8D30-9565CEB64FEA}.KINGVIEW|x86.Build.0 = Debug|Any CPU
{516316AA-8891-4741-8D30-9565CEB64FEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{516316AA-8891-4741-8D30-9565CEB64FEA}.Release|Any CPU.Build.0 = Release|Any CPU
{516316AA-8891-4741-8D30-9565CEB64FEA}.Release|x86.ActiveCfg = Release|Any CPU
{516316AA-8891-4741-8D30-9565CEB64FEA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {38B292E0-B3E7-4608-9912-1B057C2A508F}
EndGlobalSection
EndGlobal

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OpenApiGenerateDocuments>true</OpenApiGenerateDocuments>
</PropertyGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Application\ThingsGateway.Application.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,125 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>ThingsGateway.ApiController</name>
</assembly>
<members>
<member name="T:ThingsGateway.ApiController.CollectDbInfoControler">
<summary>
采集设备
</summary>
</member>
<member name="M:ThingsGateway.ApiController.CollectDbInfoControler.#ctor(Microsoft.Extensions.DependencyInjection.IServiceScopeFactory,ThingsGateway.Application.IVariableService,ThingsGateway.Application.ICollectDeviceService)">
<inheritdoc cref="T:ThingsGateway.ApiController.CollectDbInfoControler"/>
</member>
<member name="M:ThingsGateway.ApiController.CollectDbInfoControler.GetCollectDeviceList(ThingsGateway.Application.CollectDevicePageInput)">
<summary>
获取采集设备信息
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.ApiController.CollectDbInfoControler.GetVariableList(ThingsGateway.Application.VariablePageInput)">
<summary>
获取变量信息
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.ApiController.FileController">
<summary>
文件下载
</summary>
</member>
<member name="M:ThingsGateway.ApiController.FileController.#ctor(ThingsGateway.Application.IRpcLogService,ThingsGateway.Application.IBackendLogService,ThingsGateway.Application.ICollectDeviceService,ThingsGateway.Application.IUploadDeviceService,ThingsGateway.Application.IVariableService)">
<summary>
<inheritdoc cref="T:ThingsGateway.ApiController.FileController"/>
</summary>
</member>
<member name="M:ThingsGateway.ApiController.FileController.DownloadRpcLogAsync(ThingsGateway.Application.RpcLogInput)">
<summary>
下载RPC日志
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.ApiController.FileController.DownloadBackendLogAsync(ThingsGateway.Application.BackendLogInput)">
<summary>
下载后台日志
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.ApiController.FileController.DownloadCollectDeviceAsync(ThingsGateway.Application.CollectDeviceInput)">
<summary>
导出采集设备
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.ApiController.FileController.DownloadUploadDeviceAsync(ThingsGateway.Application.UploadDeviceInput)">
<summary>
导出上传设备
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.ApiController.FileController.DownloadDeviceVariableAsync(ThingsGateway.Application.DeviceVariableInput)">
<summary>
导出采集变量
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.ApiController.FileController.DownloadMemoryVariableAsync(ThingsGateway.Application.MemoryVariableInput)">
<summary>
导出内存变量
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.ApiController.RpcControler">
<summary>
设备控制
</summary>
</member>
<member name="M:ThingsGateway.ApiController.RpcControler.#ctor(Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)">
<inheritdoc cref="T:ThingsGateway.ApiController.RpcControler"/>
</member>
<member name="M:ThingsGateway.ApiController.RpcControler.ConfigDeviceThread(System.Int64,System.Boolean)">
<summary>
控制采集线程启停
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.ApiController.RpcControler.UpDeviceThread(System.Int64)">
<summary>
重启采集线程
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.ApiController.RpcControler.WriteDeviceMethods(System.Collections.Generic.Dictionary{System.String,System.String})">
<summary>
写入多个变量
</summary>
</member>
<member name="T:ThingsGateway.Web.Foundation.CollectInfoControler">
<summary>
采集状态信息
</summary>
</member>
<member name="M:ThingsGateway.Web.Foundation.CollectInfoControler.#ctor(Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)">
<inheritdoc cref="T:ThingsGateway.Web.Foundation.CollectInfoControler"/>
</member>
<member name="M:ThingsGateway.Web.Foundation.CollectInfoControler.GetCollectDeviceList">
<summary>
获取设备信息
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Web.Foundation.CollectInfoControler.GetDeviceVariableList(ThingsGateway.Application.VariablePageInput)">
<summary>
获取变量信息
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Web.Foundation.CollectInfoControler.GetRealAlarmList(ThingsGateway.Application.VariablePageInput)">
<summary>
获取实时报警信息
</summary>
<returns></returns>
</member>
</members>
</doc>

View File

@@ -1,133 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using Yitter.IdGenerator;
namespace ThingsGateway.Application;
/// <summary>
/// 缓存帮助类
/// </summary>
public class CacheDb
{
private readonly string Id;
/// <summary>
/// 构造函数传入Id号作为Sqlite文件名称
/// </summary>
/// <param name="id"></param>
public CacheDb(string id)
{
Id = id;
Directory.CreateDirectory(Path.Combine(AppContext.BaseDirectory, "CacheDb"));
if (!File.Exists(Path.Combine(AppContext.BaseDirectory, "CacheDb", $"{Id}.db")))
{
GetCacheDb().DbMaintenance.CreateDatabase();//创建数据库
GetCacheDb().CodeFirst.InitTables(typeof(CacheTable));
}
}
/// <summary>
/// 获取数据库链接
/// </summary>
/// <returns></returns>
private SqlSugarClient GetCacheDb()
{
var configureExternalServices = new ConfigureExternalServices
{
EntityService = (type, column) => // 修改列可空-1、带?问号 2、String类型若没有Required
{
if ((type.PropertyType.IsGenericType && type.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
|| (type.PropertyType == typeof(string) && type.GetCustomAttribute<RequiredAttribute>() == null))
column.IsNullable = true;
},
};
var sqlSugarClient = new SqlSugarClient(new ConnectionConfig()
{
ConnectionString = $"Data Source=CacheDb/{Id}.db;",//连接字符串
DbType = DbType.Sqlite,//数据库类型
IsAutoCloseConnection = true, //不设成true要手动close
ConfigureExternalServices = configureExternalServices,
}
);
return sqlSugarClient;
}
/// <summary>
/// 获取缓存表前n条
/// </summary>
/// <returns></returns>
public async Task<List<CacheTable>> GetCacheData(int take)
{
var db = GetCacheDb();
var data = await db.Queryable<CacheTable>().Take(take).ToListAsync();
return data;
}
/// <summary>
/// 获取缓存表全部
/// </summary>
/// <returns></returns>
public async Task<List<CacheTable>> GetCacheData()
{
var db = GetCacheDb();
var data = await db.Queryable<CacheTable>().ToListAsync();
return data;
}
/// <summary>
/// 增加离线缓存限制表最大默认2000行
/// </summary>
/// <returns></returns>
public async Task<bool> AddCacheData(string topic, string data, int max = 2000)
{
var db = GetCacheDb();
var count = await db.Queryable<CacheTable>().CountAsync();
if (count > max)
{
var data1 = await db.Queryable<CacheTable>().OrderBy(a => a.Id).Take(count - max).ToListAsync();
await db.Deleteable(data1).ExecuteCommandAsync();
}
var result = await db.Insertable(new CacheTable() { Id = YitIdHelper.NextId(), Topic = topic, CacheStr = data }).ExecuteCommandAsync();
return result > 0;
}
/// <summary>
/// 清除离线缓存
/// </summary>
/// <returns></returns>
public async Task<bool> DeleteCacheData(params long[] data)
{
var db = GetCacheDb();
var result = await db.Deleteable<CacheTable>().In(data).ExecuteCommandAsync();
return result > 0;
}
}
/// <summary>
/// 缓存表
/// </summary>
public class CacheTable
{
/// <summary>
/// Id
/// </summary>
[SugarColumn(IsPrimaryKey = true)]
public long Id { get; set; }
/// <summary>
/// Topic
/// </summary>
public string Topic { get; set; }
/// <summary>
/// 缓存值
/// </summary>
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string CacheStr { get; set; }
}

View File

@@ -1,42 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Application;
/// <summary>
/// 采集设备表
/// </summary>
[SugarTable("collectDevice", TableDescription = "采集设备表")]
[Tenant(ThingsGatewayConst.DB_ThingsGateway)]
[SugarIndex("unique_collectdevice_name", nameof(CollectDevice.Name), OrderByType.Asc, true)]
public class CollectDevice : UploadDevice
{
#region
/// <summary>
/// 是否冗余
/// </summary>
[SugarColumn(ColumnName = "IsRedundant", ColumnDescription = "是否冗余")]
[DataTable(Order = 9, IsShow = true, Sortable = true)]
public bool IsRedundant { get; set; }
/// <summary>
/// 冗余设备Id,只能选择相同驱动
/// </summary>
[SugarColumn(ColumnName = "RedundantDeviceId", ColumnDescription = "冗余设备Id")]
[IgnoreExcel]
public long RedundantDeviceId { get; set; }
#endregion
}

View File

@@ -1,71 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using ThingsGateway.Foundation;
namespace ThingsGateway.Application;
/// <summary>
/// 设备变量表
/// </summary>
[SugarTable("deviceVariable", TableDescription = "设备变量表")]
[Tenant(ThingsGatewayConst.DB_ThingsGateway)]
[SugarIndex("index_device", nameof(DeviceVariable.DeviceId), OrderByType.Asc)]
[SugarIndex("unique_deviceVariable_name", nameof(DeviceVariable.Name), OrderByType.Asc, true)]
public class DeviceVariable : MemoryVariable
{
/// <summary>
/// 设备
/// </summary>
[SugarColumn(ColumnName = "DeviceId", ColumnDescription = "设备")]
[DataTable(Order = 3, IsShow = true, Sortable = true)]
[IgnoreExcel]
public virtual long DeviceId { get; set; }
/// <summary>
/// 单位
/// </summary>
[SugarColumn(ColumnName = "Unit", ColumnDescription = "单位", Length = 200, IsNullable = true)]
[DataTable(Order = 3, IsShow = true, Sortable = true)]
public string Unit { get; set; }
/// <summary>
/// 执行间隔
/// </summary>
[SugarColumn(ColumnName = "InvokeInterval", ColumnDescription = "执行间隔")]
[DataTable(Order = 3, IsShow = true, Sortable = true)]
public virtual int IntervalTime { get; set; }
/// <summary>
/// 其他方法若不为空此时Address为方法参数
/// </summary>
[SugarColumn(ColumnName = "OtherMethod", ColumnDescription = "特殊方法", Length = 200, IsNullable = true)]
[DataTable(Order = 7, IsShow = true, Sortable = true)]
public string OtherMethod { get; set; }
/// <summary>
/// 变量地址,可能带有额外的信息,比如<see cref="DataFormat"/> ,以;分割
/// </summary>
[SugarColumn(ColumnName = "VariableAddress", ColumnDescription = "变量地址", Length = 200, IsNullable = true)]
[DataTable(Order = 3, IsShow = true, Sortable = true)]
public string VariableAddress { get; set; }
/// <summary>
/// 是否中间变量
/// </summary>
[SugarColumn(ColumnName = "IsMemoryVariable", ColumnDescription = "是否中间变量", IsNullable = false)]
[IgnoreExcel]
public override bool IsMemoryVariable { get; set; } = false;
}

View File

@@ -1,52 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using SqlSugar.DbConvert;
namespace ThingsGateway.Application;
/// <summary>
/// 插件信息表
/// </summary>
[SugarTable("driverPlugin", TableDescription = "插件信息表")]
[Tenant(ThingsGatewayConst.DB_ThingsGateway)]
[SugarIndex("unique_driverplugin_name", nameof(DriverPlugin.AssembleName), OrderByType.Asc, true)]
public class DriverPlugin : BaseEntity
{
/// <summary>
/// 文件名称
/// </summary>
[SugarColumn(ColumnName = "FileName", ColumnDescription = "文件名称")]
[DataTable(Order = 1, IsShow = true, Sortable = true)]
public string FileName { get; set; }
/// <summary>
/// 插件类名称
/// </summary>
[SugarColumn(ColumnName = "AssembleName", ColumnDescription = "插件类名称")]
[DataTable(Order = 2, IsShow = true, Sortable = true)]
public string AssembleName { get; set; }
/// <summary>
/// 插件类型
/// </summary>
[SugarColumn(ColumnDataType = "varchar(50)", ColumnName = "DriverTypeEnum", ColumnDescription = "插件类型", SqlParameterDbType = typeof(EnumToStringConvert))]
[DataTable(Order = 3, IsShow = true, Sortable = true)]
public DriverEnum DriverTypeEnum { get; set; }
/// <summary>
/// 插件文件全路径
/// </summary>
[SugarColumn(ColumnName = "FilePath", ColumnDescription = "插件文件全路径")]
[DataTable(Order = 4, IsShow = true, Sortable = true, CellClass = " table-text-truncate ")]
public string FilePath { get; set; }
}

View File

@@ -1,188 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.DependencyInjection;
using Hardware.Info;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.ComponentModel;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text;
namespace ThingsGateway.Application;
/// <summary>
/// 硬件信息获取
/// </summary>
public class HardwareInfoService : ISingleton
{
/// <summary>
/// 硬件信息获取
/// </summary>
public HardwareInfo HardwareInfo { get; private set; }
private readonly ILogger _logger;
/// <inheritdoc cref="HardwareInfoService"/>
public HardwareInfoService(ILogger<HardwareInfoService> logger)
{
_logger = logger;
}
/// <summary>
/// 循环获取
/// </summary>
/// <returns></returns>
public void Init()
{
try
{
HardwareInfo = new();
}
catch (Exception ex)
{
_logger.LogError(ex, "初始化硬件信息失败");
}
Task.Factory.StartNew(async () =>
{
string currentPath = Directory.GetCurrentDirectory();
DriveInfo drive = new(Path.GetPathRoot(currentPath));
appInfo = new()
{
DriveInfo = drive,
HostName = Environment.MachineName, // 主机名称
SystemOs = RuntimeInformation.OSDescription, // 操作系统
OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(), // 系统架构
RemoteIp = await GetIpFromOnlineAsync(), // 外网地址
FrameworkDescription = RuntimeInformation.FrameworkDescription, // NET框架
Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production",
Stage = App.HostEnvironment.IsStaging() ? "Stage" : "非Stage", // 是否Stage环境
UpdateTime = SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(),
};
while (true)
{
try
{
appInfo.UpdateTime = SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat();
appInfo.RemoteIp = await GetIpFromOnlineAsync();
HardwareInfo?.RefreshMemoryStatus();
HardwareInfo?.RefreshMemoryList();
HardwareInfo?.RefreshNetworkAdapterList();
HardwareInfo?.RefreshCPUList();
//10秒更新一次
await Task.Delay(10000);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "获取硬件信息失败");
}
}
}
, TaskCreationOptions.LongRunning);
}
private APPInfo appInfo = new();
/// <summary>
/// 运行信息获取
/// </summary>
public APPInfo APPInfo => appInfo;
/// <summary>
/// IP地址信息
/// </summary>
/// <returns></returns>
public async Task<string> GetIpFromOnlineAsync()
{
try
{
var url = "http://myip.ipip.net";
using var httpClient = new HttpClient();
using var stream = await httpClient.GetStreamAsync(url);
using var streamReader = new StreamReader(stream, Encoding.UTF8);
var html = streamReader.ReadToEnd();
return html.Replace("当前 IP", "").Replace("来自于:", "");
}
catch (Exception)
{
return "";
}
}
}
/// <inheritdoc/>
public class APPInfo
{
/// <summary>
/// 主机环境
/// </summary>
[Description("主机环境")]
public string Environment { get; set; }
/// <summary>
/// NET框架
/// </summary>
[Description("NET框架")]
public string FrameworkDescription { get; set; }
/// <summary>
/// 主机名称
/// </summary>
[Description("主机名称")]
public string HostName { get; set; }
/// <summary>
/// 系统架构
/// </summary>
[Description("系统架构")]
public string OsArchitecture { get; set; }
/// <summary>
/// 外网地址
/// </summary>
[Description("外网地址")]
public string RemoteIp { get; set; }
/// <summary>
/// Stage环境
/// </summary>
[Description("Stage环境")]
public string Stage { get; set; }
/// <summary>
/// 操作系统
/// </summary>
[Description("操作系统")]
public string SystemOs { get; set; }
/// <summary>
/// 更新时间
/// </summary>
[Description("更新时间")]
public string UpdateTime { get; set; }
/// <summary>
/// 当前磁盘信息
/// </summary>
[Description("当前磁盘信息")]
public DriveInfo DriveInfo { get; set; }
}

View File

@@ -1,207 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.Net;
using System.Reflection;
using System.Text.RegularExpressions;
using ThingsGateway.Foundation;
namespace ThingsGateway.Application;
/// <summary>
/// 读写扩展
/// </summary>
public static class ReadWriteHelpers
{
/// <summary>
/// 根据数据类型写入设备只支持C#内置数据类型,但不包含<see cref="decimal"/>和<see cref="char"/>和<see cref="sbyte"/>
/// </summary>
/// <returns></returns>
public static object GetObjectData(this string value)
{
//判断数值类型
Regex regex = new("^[-+]?[0-9]*\\.?[0-9]+$");
bool match = regex.IsMatch(value);
if (match)
{
if (value.ToDecimal() == 0 && Convert.ToInt64(value) != 0)
{
throw new("转换失败");
}
return value.ToDecimal();
}
else if (value.IsBoolValue())
{
return value.GetBoolValue();
}
else
{
return value;
}
}
/// <summary>
/// 根据<see cref="OperResult.IsSuccess"/>执行action
/// </summary>
public static OperResult<T> DealWithReadResult<T>(OperResult<T> read, Action<T> action)
{
if (!read.IsSuccess || action == null)
return read;
action(read.Content);
return read;
}
/// <summary>
/// 根据<see cref="PropertyInfo"/> 数据类型转化返回值类型
/// </summary>
/// <param name="p"></param>
/// <param name="value"></param>
/// <returns></returns>
public static object ObjToTypeValue(this PropertyInfo p, string value)
{
object _value = null;
if (p.PropertyType == typeof(bool))
_value = value.GetBoolValue();
else if (p.PropertyType == typeof(byte))
_value = byte.Parse(value);
else if (p.PropertyType == typeof(sbyte))
_value = sbyte.Parse(value);
else if (p.PropertyType == typeof(short))
_value = short.Parse(value);
else if (p.PropertyType == typeof(ushort))
_value = ushort.Parse(value);
else if (p.PropertyType == typeof(int))
_value = int.Parse(value);
else if (p.PropertyType == typeof(uint))
_value = uint.Parse(value);
else if (p.PropertyType == typeof(long))
_value = long.Parse(value);
else if (p.PropertyType == typeof(ulong))
_value = ulong.Parse(value);
else if (p.PropertyType == typeof(float))
_value = float.Parse(value);
else if (p.PropertyType == typeof(double))
_value = double.Parse(value);
else if (p.PropertyType == typeof(decimal))
_value = decimal.Parse(value);
else if (p.PropertyType == typeof(DateTime))
_value = DateTime.Parse(value);
else if (p.PropertyType == typeof(DateTimeOffset))
_value = DateTimeOffset.Parse(value);
else if (p.PropertyType == typeof(string))
_value = value;
else if (p.PropertyType == typeof(IPAddress))
_value = IPAddress.Parse(value);
else if (p.PropertyType.IsEnum)
_value = Enum.Parse(p.PropertyType, value);
return _value;
}
/// <summary>
/// 在返回的字节数组中解析每个变量的值
/// 根据每个变量的<see cref="DeviceVariableRunTime.Index"/>
/// 不支持变长字符串类型变量一定不能存在于变量List中
/// </summary>
/// <param name="buffer">返回的字节数组</param>
/// <param name="values">设备变量List</param>
/// <param name="startIndex">开始序号</param>
public static void PraseStructContent(byte[] buffer, IList<DeviceVariableRunTime> values, int startIndex = 0)
{
foreach (DeviceVariableRunTime organizedVariable in values)
{
var deviceValue = organizedVariable;
IThingsGatewayBitConverter byteConverter = deviceValue.ThingsGatewayBitConverter;
Type propertyType = organizedVariable.DataType;
int index = organizedVariable.Index;
if (propertyType == typeof(bool))
{
if (index + startIndex > buffer.Length * 8)
throw new Exception($"返回数据长度{buffer.Length}不足以转换为对应索引位{index + startIndex}的数据,请检查数据类型");
}
else
{
if (index + startIndex > buffer.Length)
throw new Exception($"返回数据长度{buffer.Length}不足以转换为对应索引位{index + startIndex}的数据,请检查数据类型");
}
if (propertyType == typeof(byte))
{
byte num = byteConverter.ToByte(buffer, index + startIndex);
Set(organizedVariable, num);
}
else if (propertyType == typeof(short))
{
short num = byteConverter.ToInt16(buffer, index + startIndex);
Set(organizedVariable, num);
}
else if (propertyType == typeof(ushort))
{
ushort num = byteConverter.ToUInt16(buffer, index + startIndex);
Set(organizedVariable, num);
}
else if (propertyType == typeof(int))
{
int num = byteConverter.ToInt32(buffer, index + startIndex);
Set(organizedVariable, num);
}
else if (propertyType == typeof(uint))
{
uint num = byteConverter.ToUInt32(buffer, index + startIndex);
Set(organizedVariable, num);
}
else if (propertyType == typeof(long))
{
long num = byteConverter.ToInt64(buffer, index + startIndex);
Set(organizedVariable, num);
}
else if (propertyType == typeof(ulong))
{
ulong num = byteConverter.ToUInt64(buffer, index + startIndex);
Set(organizedVariable, num);
}
else if (propertyType == typeof(float))
{
float num = byteConverter.ToSingle(buffer, index + startIndex);
Set(organizedVariable, num);
}
else if (propertyType == typeof(double))
{
double num = byteConverter.ToDouble(buffer, index + startIndex);
Set(organizedVariable, num);
}
else if (propertyType == typeof(bool))
{
bool num = byteConverter.ToBoolean(buffer, index + (startIndex * 8));
Set(organizedVariable, num);
}
else if (propertyType == typeof(string))
{
string num = byteConverter.ToString(buffer, index + startIndex, byteConverter.StringLength);
Set(organizedVariable, num);
}
}
static void Set(DeviceVariableRunTime organizedVariable, object num)
{
var operResult = organizedVariable.SetValue(num); ;
if (!operResult.IsSuccess)
{
throw new Exception(operResult.Message);
}
}
}
}

View File

@@ -1,197 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
using ThingsGateway.Foundation;
namespace ThingsGateway.Application;
/// <summary>
/// ManageGatewayConfig
/// </summary>
public class ManageGatewayConfig
{
/// <summary>
/// 启用
/// </summary>
[Description("启用")]
public bool Enable { get; set; }
/// <summary>
/// MqttBrokerIP
/// </summary>
[Description("Mqtt-Tcp IP")]
public string MqttBrokerIP { get; set; }
/// <summary>
/// MqttBrokerPort
/// </summary>
[Description("Mqtt-Tcp 端口")]
public int MqttBrokerPort { get; set; }
/// <summary>
/// UserName
/// </summary>
[Description("Mqtt用户名")]
public string UserName { get; set; }
/// <summary>
/// Password
/// </summary>
[Description("Mqtt密码")]
public string Password { get; set; }
/// <summary>
/// WriteRpcTopicRpc返回为{WriteRpcTopic}/Return只有这个topic才开放外部订阅权限
/// </summary>
[Description("变量写入Rpc主题")]
public string WriteRpcTopic { get; set; }
/// <summary>
/// DBDownTopic
/// </summary>
[Description("配置下发Rpc主题")]
public string DBDownTopic { get; set; }
/// <summary>
/// DBUploadTopic
/// </summary>
[Description("配置上传Rpc主题")]
public string DBUploadTopic { get; set; }
}
/// <summary>
/// ClientGatewayConfig
/// </summary>
public class ClientGatewayConfig : ManageGatewayConfig
{
/// <summary>
/// 标识
/// </summary>
[Description("子网关标识ID")]
public string GatewayId { get; set; }
}
/// <summary>
/// 用于Mqtt Json传输上传/下载配置信息
/// </summary>
public class MqttDBUploadRpcResult
{
/// <summary>
/// 采集设备
/// </summary>
public List<CollectDevice> CollectDevices { get; set; } = new();
/// <summary>
/// 上传设备
/// </summary>
public List<UploadDevice> UploadDevices { get; set; } = new();
/// <summary>
/// 变量
/// </summary>
public List<DeviceVariable> DeviceVariables { get; set; } = new();
}
/// <summary>
/// 用于Mqtt Json传输上传/下载配置信息
/// </summary>
public class MqttDBDownRpc
{
/// <summary>
/// 采集设备
/// </summary>
public byte[] CollectDevices { get; set; }
/// <summary>
/// 上传设备
/// </summary>
public byte[] UploadDevices { get; set; }
/// <summary>
/// 变量
/// </summary>
public byte[] DeviceVariables { get; set; }
/// <summary>
/// true=>删除全部后增加
/// </summary>
[Description("是否删除原采集设备表")]
public bool IsCollectDevicesFullUp { get; set; }
/// <summary>
/// true=>删除全部后增加
/// </summary>
[Description("是否删除原上传设备表")]
public bool IsUploadDevicesFullUp { get; set; }
/// <summary>
/// true=>删除全部后增加
/// </summary>
[Description("是否删除原变量表")]
public bool IsDeviceVariablesFullUp { get; set; }
/// <summary>
/// 是否立即重启,使配置生效
/// </summary>
[Description("是否重启子网关线程")]
public bool IsRestart { get; set; }
}
/// <summary>
/// MqttRpc传入
/// </summary>
public class ManageMqttRpcFrom
{
/// <summary>
/// 标识
/// </summary>
public string GatewayId { get; set; }
/// <summary>
/// 标识
/// </summary>
public string RpcId { get; set; }
/// <summary>
/// "WriteInfos":{"test":"1"}
/// </summary>
public Dictionary<string, string> WriteInfos { get; set; } = new();
}
/// <summary>
/// MqttRpc输出
/// </summary>
public class ManageMqttRpcResult
{
/// <summary>
/// 标识
/// </summary>
public string GatewayId { get; set; }
/// <summary>
/// 标识
/// </summary>
public string RpcId { get; set; }
/// <summary>
/// 消息
/// </summary>
public Dictionary<string, OperResult> Message { get; set; } = new();
/// <summary>
/// 是否成功
/// </summary>
public bool Success { get; set; }
}

View File

@@ -1,838 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.Logging.Extensions;
using Mapster;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
using System.Collections.Concurrent;
using System.Net;
using System.Text;
using ThingsGateway.Foundation;
using TouchSocket.Core;
namespace ThingsGateway.Application;
/// <summary>
/// ManageGatewayWorker
/// </summary>
public class ManageGatewayWorker : BackgroundService
{
private readonly ILogger _clientLogger;
private readonly ILogger _logger;
private readonly ILogger _manageLogger;
/// <summary>
/// 全部重启锁
/// </summary>
private readonly EasyLock restartLock = new();
private IMqttClient _mqttClient;
private MqttServer _mqttServer;
private MqttClientSubscribeOptions _mqttSubscribeOptions;
/// <inheritdoc cref="ManageGatewayWorker"/>
public ManageGatewayWorker(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger("ManageGatewayWorker");
_manageLogger = loggerFactory.CreateLogger("管理网关(mqttBroker)");
_clientLogger = loggerFactory.CreateLogger("子网关(mqttClient)");
}
/// <summary>
/// 服务状态
/// </summary>
public OperResult ClientStatuString { get; set; } = new OperResult("初始化");
/// <summary>
/// 服务状态
/// </summary>
public OperResult ManageStatuString { get; set; } = new OperResult("初始化");
#region worker服务
/// <inheritdoc/>
public override async Task StartAsync(CancellationToken token)
{
_logger?.LogInformation("ManageGatewayWorker启动");
await RestartAsync();
await base.StartAsync(token);
}
/// <inheritdoc/>
public override async Task StopAsync(CancellationToken token)
{
_logger?.LogInformation("ManageGatewayWorker停止");
await StopAsync();
await base.StopAsync(token);
}
/// <inheritdoc/>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
if (_mqttClient != null)
{
//持续重连
var result = await TryMqttClientAsync(stoppingToken);
if (result.IsSuccess)
{
_clientLogger.LogDebug("连接正常:" + result.Message);
ClientStatuString.ResultCode = ResultCode.Success;
ClientStatuString.Message = "连接正常:" + result.Message;
}
else
{
_clientLogger.LogWarning("连接错误:" + result.Message);
ClientStatuString.ResultCode = ResultCode.Fail;
ClientStatuString.Message = "连接错误:" + result.Message;
}
}
await Task.Delay(10000, stoppingToken);
//if (_mqttServer != null)
//{
// //TODO:test code
// var mqttClientStatuses = await _mqttServer.GetClientsAsync();
// if (mqttClientStatuses.FirstOrDefault() is MqttClientStatus mqttClientStatus)
// {
// //获取子网关信息
// var getClientGatewayDBResult = await GetClientGatewayDBAsync(mqttClientStatus.Id);
// //下发子网关配置
// var mqttDBDownRpc = new MqttDBDownRpc
// {
// IsRestart = true
// };
// var setClientGatewayDBResult = await SetClientGatewayDBAsync(mqttClientStatus.Id, mqttDBDownRpc);
// //下发子网关配置
// var manageMqttRpcFrom = new ManageMqttRpcFrom
// {
// WriteInfos = new Dictionary<string, string>()
// {
// {
// "test41","123"
// }
// },
// GatewayId = "GatewayId",
// RpcId = "123456",
// };
// var WriteVariableResult = await WriteVariableAsync(manageMqttRpcFrom);
// }
//}
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
_logger.LogError(ex, ToString());
}
}
}
#endregion
#region public
/// <summary>
/// 获取子网关的配置信息
/// </summary>
/// <returns></returns>
public async Task<OperResult<MqttDBUploadRpcResult>> GetClientGatewayDBAsync(string gatewayId, int timeOut = 3000, CancellationToken token = default)
{
try
{
var buffer = Encoding.UTF8.GetBytes(string.Empty);
var response = await RpcDataExecuteAsync(gatewayId, ClientGatewayConfig.DBUploadTopic, buffer, timeOut, MqttQualityOfServiceLevel.AtMostOnce, token);
var data = Encoding.UTF8.GetString(response).FromJsonString<MqttDBUploadRpcResult>();
return OperResult.CreateSuccessResult(data);
}
catch (Exception ex)
{
return new OperResult<MqttDBUploadRpcResult>(ex);
}
}
/// <summary>
/// 重启
/// </summary>
/// <returns></returns>
public async Task RestartAsync()
{
await StopAsync();
await StartAsync();
}
/// <summary>
/// 下载配置信息到子网关
/// </summary>
/// <returns></returns>
public async Task<OperResult> SetClientGatewayDBAsync(string gatewayId, MqttDBDownRpc mqttDBRpc, int timeOut = 3000, CancellationToken token = default)
{
try
{
var buffer = Encoding.UTF8.GetBytes(mqttDBRpc?.ToJsonString() ?? string.Empty);
var response = await RpcDataExecuteAsync(gatewayId, ClientGatewayConfig.DBDownTopic, buffer, timeOut, MqttQualityOfServiceLevel.AtMostOnce, token);
var data = Encoding.UTF8.GetString(response).FromJsonString<OperResult>();
return data;
}
catch (Exception ex)
{
return new OperResult(ex);
}
}
/// <summary>
/// 写入变量到子网关
/// </summary>
/// <returns></returns>
public async Task<OperResult<ManageMqttRpcResult>> WriteVariableAsync(ManageMqttRpcFrom manageMqttRpcFrom, int timeOut = 3000, CancellationToken token = default)
{
try
{
var payload = Encoding.UTF8.GetBytes(manageMqttRpcFrom?.ToJsonString() ?? string.Empty);
var requestTopic = ManageGatewayConfig.WriteRpcTopic;
var responseTopic = GetRpcReturnTopic(ManageGatewayConfig.WriteRpcTopic);
var key = GetRpcReturnIdTopic(manageMqttRpcFrom.GatewayId, requestTopic, manageMqttRpcFrom.RpcId);
ManageMqttRpcResult result = await RpcWriteExecuteAsync(timeOut, payload, requestTopic, key, token);
return OperResult.CreateSuccessResult(result);
}
catch (Exception ex)
{
return new OperResult<ManageMqttRpcResult>(ex);
}
}
/// <summary>
/// 获取子网关列表
/// </summary>
/// <returns></returns>
public async Task<List<MqttClientStatus>> GetClientGatewayAsync()
{
if (_mqttServer != null)
{
var data = await _mqttServer.GetClientsAsync();
return data.ToList();
}
else
{
return new List<MqttClientStatus>();
}
}
#endregion
#region RPC实现
readonly ConcurrentDictionary<string, WaitDataAsync<byte[]>> _waitingCalls = new();
readonly ConcurrentDictionary<string, WaitDataAsync<ManageMqttRpcResult>> _writerRpcResultWaitingCalls = new();
private readonly EasyLock clientLock = new();
private async Task<ManageMqttRpcResult> RpcWriteExecuteAsync(int timeOut, byte[] payload, string requestTopic, string key, CancellationToken token)
{
try
{
using WaitDataAsync<ManageMqttRpcResult> waitDataAsync = new();
if (!_writerRpcResultWaitingCalls.TryAdd(key, waitDataAsync))
{
throw new InvalidOperationException();
}
waitDataAsync.SetCancellationToken(token);
//请求子网关的数据
var message = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).Build();
await _mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(message), token);
var result = await waitDataAsync.WaitAsync(timeOut);
switch (result)
{
case WaitDataStatus.SetRunning:
return waitDataAsync.WaitResult;
case WaitDataStatus.Overtime:
throw new TimeoutException();
case WaitDataStatus.Canceled:
{
throw new Exception("等待已终止。可能是客户端已掉线,或者被注销。");
}
case WaitDataStatus.Default:
case WaitDataStatus.Disposed:
default:
throw new Exception(ThingsGatewayStatus.UnknownError.GetDescription());
}
}
finally
{
_writerRpcResultWaitingCalls.Remove(key);
}
}
/// <summary>
/// RPC请求子网关并返回需要传入子网关ID作为Topic参数一部分
/// </summary>
/// <returns></returns>
private async Task<byte[]> RpcDataExecuteAsync(string gatewayId, string topic, byte[] payload, int timeOut, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken token = default)
{
var responseTopic = GetRpcReturnTopic(gatewayId, topic);
var requestTopic = GetRpcTopic(gatewayId, topic);
try
{
using WaitDataAsync<byte[]> waitDataAsync = new();
if (!_waitingCalls.TryAdd(responseTopic, waitDataAsync))
{
throw new InvalidOperationException();
}
waitDataAsync.SetCancellationToken(token);
//请求子网关的数据
var message = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).Build();
await _mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(message), token);
var result = await waitDataAsync.WaitAsync(timeOut);
switch (result)
{
case WaitDataStatus.SetRunning:
return waitDataAsync.WaitResult;
case WaitDataStatus.Overtime:
throw new TimeoutException();
case WaitDataStatus.Canceled:
{
throw new Exception("等待已终止。可能是客户端已掉线,或者被注销。");
}
case WaitDataStatus.Default:
case WaitDataStatus.Disposed:
default:
throw new Exception(ThingsGatewayStatus.UnknownError.GetDescription());
}
}
finally
{
_waitingCalls.Remove(responseTopic);
}
}
#endregion
#region
internal async Task StartAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
await InitAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "启动错误");
}
finally
{
restartLock.Release();
}
}
internal async Task StopAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
_mqttClient?.SafeDispose();
_mqttServer?.SafeDispose();
_mqttClient = null;
_mqttServer = null;
}
catch (Exception ex)
{
_logger.LogError(ex, "停止错误");
}
finally
{
restartLock.Release();
}
}
/// <summary>
/// 初始化
/// </summary>
private async Task InitAsync()
{
try
{
ManageGatewayConfig = App.GetConfig<ManageGatewayConfig>("ManageGatewayConfig");
if (ManageGatewayConfig?.Enable != true)
{
ManageStatuString = new OperResult($"已退出:不启用管理功能");
_manageLogger.LogWarning("已退出:不启用管理功能");
}
else
{
var mqttFactory = new MqttFactory(new MqttNetLogger(_manageLogger));
var mqttServerOptions = mqttFactory.CreateServerOptionsBuilder()
.WithDefaultEndpointBoundIPAddress(string.IsNullOrEmpty(ManageGatewayConfig.MqttBrokerIP) ? null : IPAddress.Parse(ManageGatewayConfig.MqttBrokerIP))
.WithDefaultEndpointPort(ManageGatewayConfig.MqttBrokerPort)
.WithDefaultEndpoint()
.Build();
_mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions);
if (_mqttServer != null)
{
_mqttServer.ValidatingConnectionAsync += MqttServer_ValidatingConnectionAsync;//认证
_mqttServer.InterceptingPublishAsync += MqttServer_InterceptingPublishAsync;//消息
await _mqttServer.StartAsync();
}
ManageStatuString = OperResult.CreateSuccessResult();
}
}
catch (Exception ex)
{
_manageLogger.LogError(ex, "初始化失败");
ManageStatuString = new($"初始化失败-{ex.Message}");
}
try
{
ClientGatewayConfig = App.GetConfig<ClientGatewayConfig>("ClientGatewayConfig");
if (ClientGatewayConfig?.Enable != true)
{
ClientStatuString = new OperResult($"已退出:不启用子网关功能");
_clientLogger.LogWarning("已退出:不启用子网关功能");
}
else
{
var mqttFactory = new MqttFactory(new MqttNetLogger(_clientLogger));
_mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
.WithCredentials(ClientGatewayConfig.UserName, ClientGatewayConfig.Password)//账密
.WithTcpServer(ClientGatewayConfig.MqttBrokerIP, ClientGatewayConfig.MqttBrokerPort)//服务器
.WithClientId(ClientGatewayConfig.GatewayId)
.WithCleanSession(true)
.WithKeepAlivePeriod(TimeSpan.FromSeconds(120.0))
.WithoutThrowOnNonSuccessfulConnectResponse()
.Build();
_mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(
f =>
{
f.WithTopic(ClientGatewayConfig.WriteRpcTopic);
f.WithAtMostOnceQoS();
})
.WithTopicFilter(
f =>
{
f.WithTopic(GetRpcTopic(ClientGatewayConfig.GatewayId, ClientGatewayConfig.DBDownTopic));
f.WithAtMostOnceQoS();
})
.WithTopicFilter(
f =>
{
f.WithTopic(GetRpcTopic(ClientGatewayConfig.GatewayId, ClientGatewayConfig.DBUploadTopic));
f.WithAtMostOnceQoS();
})
.Build();
_mqttClient = mqttFactory.CreateMqttClient();
_mqttClient.ConnectedAsync += MqttClient_ConnectedAsync;
_mqttClient.ApplicationMessageReceivedAsync += MqttClient_ApplicationMessageReceivedAsync;
await TryMqttClientAsync(CancellationToken.None);
}
}
catch (Exception ex)
{
_clientLogger.LogError(ex, "初始化失败");
}
}
/// <summary>
/// ClientGatewayConfig
/// </summary>
public ClientGatewayConfig ClientGatewayConfig;
/// <summary>
/// ManageGatewayConfig
/// </summary>
public ManageGatewayConfig ManageGatewayConfig;
private MqttClientOptions _mqttClientOptions;
RpcSingletonService _rpcCore;
private async Task DBDownTopicMethod(MqttApplicationMessageReceivedEventArgs args)
{
var mqttDBRpc = args.ApplicationMessage.PayloadSegment.Count > 0 ? Encoding.UTF8.GetString(args.ApplicationMessage.PayloadSegment).FromJsonString<MqttDBDownRpc>() : null;
if (mqttDBRpc != null)
{
OperResult result = new();
var collectDeviceService = App.GetService<CollectDeviceService>();
var variableService = App.GetService<VariableService>();
var uploadDeviceService = App.GetService<UploadDeviceService>();
collectDeviceService.Context = variableService.Context = uploadDeviceService.Context;
var itenant = collectDeviceService.Context.AsTenant();
//事务
var dbResult = await itenant.UseTranAsync(async () =>
{
if (mqttDBRpc.IsCollectDevicesFullUp)
{
await collectDeviceService.AsDeleteable().ExecuteCommandAsync();
}
var collectDevices = new List<CollectDevice>();
if (mqttDBRpc.CollectDevices != null && mqttDBRpc.CollectDevices.Length > 0)
{
using MemoryStream stream = new(mqttDBRpc.CollectDevices);
var previewResult = await collectDeviceService.PreviewAsync(stream);
if (previewResult.FirstOrDefault().Value.HasError)
{
throw new(previewResult.Select(a => a.Value.Results.Where(a => !a.isSuccess).ToList()).ToList().ToJsonString());
}
foreach (var item in previewResult)
{
if (item.Key == ExportHelpers.CollectDeviceSheetName)
{
var collectDeviceImports = ((ImportPreviewOutput<CollectDevice>)item.Value).Data;
collectDevices = collectDeviceImports.Values.Adapt<List<CollectDevice>>();
break;
}
}
await collectDeviceService.ImportAsync(previewResult);
}
if (mqttDBRpc.IsUploadDevicesFullUp)
{
await uploadDeviceService.AsDeleteable().ExecuteCommandAsync();
}
var uploadDevices = new List<UploadDevice>();
if (mqttDBRpc.UploadDevices != null && mqttDBRpc.UploadDevices.Length > 0)
{
using MemoryStream stream1 = new(mqttDBRpc.UploadDevices);
var previewResult1 = await uploadDeviceService.PreviewAsync(stream1);
if (previewResult1.FirstOrDefault().Value.HasError)
{
throw new(previewResult1.Select(a => a.Value.Results.Where(a => !a.isSuccess).ToList()).ToList().ToJsonString());
}
foreach (var item in previewResult1)
{
if (item.Key == ExportHelpers.UploadDeviceSheetName)
{
var uploadDeviceImports = ((ImportPreviewOutput<UploadDevice>)item.Value).Data;
uploadDevices = uploadDeviceImports.Values.Adapt<List<UploadDevice>>();
break;
}
}
await uploadDeviceService.ImportAsync(previewResult1);
}
if (mqttDBRpc.IsDeviceVariablesFullUp)
{
await variableService.AsDeleteable().ExecuteCommandAsync();
}
if (mqttDBRpc.DeviceVariables != null && mqttDBRpc.DeviceVariables.Length > 0)
{
using MemoryStream stream2 = new(mqttDBRpc.DeviceVariables);
var previewResult2 = await variableService.PreviewAsync(stream2, collectDevices, uploadDevices);
if (previewResult2.FirstOrDefault().Value.HasError)
{
throw new(previewResult2.Select(a => a.Value.Results.Where(a => !a.isSuccess).ToList()).ToList().ToJsonString());
}
await variableService.ImportAsync(previewResult2);
}
});
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_UploadDevice);//cache删除
if (dbResult.IsSuccess)//如果成功了
{
_clientLogger.LogInformation("子网关接收配置,并保存至数据库-执行成功");
result = OperResult.CreateSuccessResult();
if (mqttDBRpc.IsRestart)
{
_clientLogger.LogInformation("子网关接收配置,并重启");
await ServiceHelper.GetBackgroundService<CollectDeviceWorker>().RestartDeviceThreadAsync();
}
}
else
{
//写日志
result.Message = dbResult.ErrorMessage;
}
var variableMessage = new MqttApplicationMessageBuilder()
.WithTopic(GetRpcReturnTopic(args.ApplicationMessage.Topic))
.WithPayload(result.ToJsonString()).Build();
if (_mqttClient.IsConnected)
await _mqttClient.PublishAsync(variableMessage);
}
}
private async Task DBUploadTopicMethod(MqttApplicationMessageReceivedEventArgs args)
{
MqttDBUploadRpcResult result = new();
var collectDeviceService = App.GetService<CollectDeviceService>();
var variableService = App.GetService<VariableService>();
var uploadDeviceService = App.GetService<UploadDeviceService>();
result.CollectDevices = collectDeviceService.GetCacheList(false);
result.DeviceVariables = await variableService.GetListAsync();
result.UploadDevices = uploadDeviceService.GetCacheList(false);
var variableMessage = new MqttApplicationMessageBuilder()
.WithTopic(GetRpcReturnTopic(args.ApplicationMessage.Topic))
.WithPayload(result.ToJsonString()).Build();
if (_mqttClient.IsConnected)
await _mqttClient.PublishAsync(variableMessage);
}
private string GetRpcReturnIdTopic(string gatewayId, string topic, string rpcId)
{
var responseTopic = $"{gatewayId}/{topic}/rpc/Return/rpcId";
return responseTopic;
}
private string GetRpcReturnTopic(string gatewayId, string topic)
{
var responseTopic = $"{gatewayId}/{topic}/rpc/Return";
return responseTopic;
}
private string GetRpcReturnTopic(string requestTopic)
{
var responseTopic = $"{requestTopic}/Return";
return responseTopic;
}
private string GetRpcTopic(string gatewayId, string topic)
{
var requestTopic = $"{gatewayId}/{topic}/rpc";
return requestTopic;
}
private async Task MqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs args)
{
if (args.ApplicationMessage.Topic == GetRpcTopic(ClientGatewayConfig.GatewayId, ClientGatewayConfig.DBUploadTopic))
{
_clientLogger.LogInformation("子网关配置上传");
await DBUploadTopicMethod(args);
return;
}
if (args.ApplicationMessage.Topic == GetRpcTopic(ClientGatewayConfig.GatewayId, ClientGatewayConfig.DBDownTopic))
{
_clientLogger.LogInformation("子网关接收配置,并保存至数据库");
await DBDownTopicMethod(args);
return;
}
if (args.ApplicationMessage.Topic == ClientGatewayConfig.WriteRpcTopic)
{
await WriteRpcTopicMethod(args);
return;
}
}
private async Task MqttClient_ConnectedAsync(MqttClientConnectedEventArgs args)
{
var subResult = await _mqttClient.SubscribeAsync(_mqttSubscribeOptions);
if (subResult.Items.Any(a => a.ResultCode > (MqttClientSubscribeResultCode)10))
{
_clientLogger?.LogWarning("订阅失败-" + subResult.Items
.Where(a => a.ResultCode > (MqttClientSubscribeResultCode)10)
.Select(a =>
new
{
Topic = a.TopicFilter.Topic,
ResultCode = a.ResultCode.ToString()
}
)
.ToJsonString()
);
}
}
private Task MqttServer_InterceptingPublishAsync(InterceptingPublishEventArgs eventArgs)
{
if (eventArgs.ApplicationMessage.Topic == GetRpcReturnTopic(ManageGatewayConfig.WriteRpcTopic))
{
if (!_writerRpcResultWaitingCalls.IsEmpty)
{
var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
var manageMqttRpcResult = Encoding.UTF8.GetString(payloadBuffer).FromJsonString<ManageMqttRpcResult>();
var key = GetRpcReturnIdTopic(manageMqttRpcResult.GatewayId, ManageGatewayConfig.WriteRpcTopic, manageMqttRpcResult.RpcId);
if (!_writerRpcResultWaitingCalls.TryRemove(key, out var writeRpcResultAsync))
{
return CompletedTask.Instance;
}
writeRpcResultAsync.Set(manageMqttRpcResult);
}
}
else
{
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
{
return CompletedTask.Instance;
}
var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
awaitable.Set(payloadBuffer);
}
return CompletedTask.Instance;
}
private Task MqttServer_ValidatingConnectionAsync(ValidatingConnectionEventArgs arg)
{
if (ManageGatewayConfig.UserName != arg.UserName)
{
arg.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance;
}
if (ManageGatewayConfig.Password != arg.Password)
{
arg.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance;
}
_manageLogger?.LogInformation(ToString() + "-" + arg.ClientId + "-客户端已连接成功");
return CompletedTask.Instance;
}
private async Task<OperResult> TryMqttClientAsync(CancellationToken token)
{
if (_mqttClient?.IsConnected == true)
return OperResult.CreateSuccessResult();
return await Cilent();
async Task<OperResult> Cilent()
{
if (_mqttClient?.IsConnected == true)
return OperResult.CreateSuccessResult();
try
{
await clientLock.WaitAsync();
if (_mqttClient?.IsConnected == true)
return OperResult.CreateSuccessResult();
using var timeoutToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(5000));
using CancellationTokenSource StoppingToken = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutToken.Token);
if (_mqttClient?.IsConnected == true)
return OperResult.CreateSuccessResult();
if (_mqttClient == null)
{
return new OperResult("未初始化");
}
var result = await _mqttClient?.ConnectAsync(_mqttClientOptions, StoppingToken.Token);
if (result.ResultCode == MqttClientConnectResultCode.Success)
{
return OperResult.CreateSuccessResult();
}
else
{
return new OperResult(result.ReasonString);
}
}
catch (Exception ex)
{
return new OperResult(ex);
}
finally
{
clientLock.Release();
}
}
}
private async Task WriteRpcTopicMethod(MqttApplicationMessageReceivedEventArgs args)
{
var manageMqttRpcFrom = args.ApplicationMessage.PayloadSegment.Count > 0 ? Encoding.UTF8.GetString(args.ApplicationMessage.PayloadSegment).FromJsonString<ManageMqttRpcFrom>() : null;
if (manageMqttRpcFrom != null && manageMqttRpcFrom.GatewayId == ClientGatewayConfig.GatewayId)
{
ManageMqttRpcResult mqttRpcResult = new() { RpcId = manageMqttRpcFrom.RpcId, GatewayId = manageMqttRpcFrom.GatewayId };
_rpcCore ??= App.GetService<RpcSingletonService>();
var result = await _rpcCore.InvokeDeviceMethodAsync("子网关RPC" + "-" + args.ClientId,
manageMqttRpcFrom.WriteInfos.Where(
a => !mqttRpcResult.Message.Any(b => b.Key == a.Key)).ToDictionary(a => a.Key, a => a.Value));
mqttRpcResult.Message.AddRange(result);
mqttRpcResult.Success = !mqttRpcResult.Message.Any(a => !a.Value.IsSuccess);
var variableMessage = new MqttApplicationMessageBuilder()
.WithTopic(GetRpcReturnTopic(args.ApplicationMessage.Topic))
.WithPayload(mqttRpcResult.ToJsonString()).Build();
if (_mqttClient.IsConnected)
await _mqttClient.PublishAsync(variableMessage);
}
}
#endregion
}

View File

@@ -1,50 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Microsoft.Extensions.Logging;
using MQTTnet.Diagnostics;
namespace ThingsGateway.Application;
internal class MqttNetLogger : IMqttNetLogger
{
readonly ILogger LogMessage;
public MqttNetLogger(ILogger logger)
{
LogMessage = logger;
}
public bool IsEnabled => true;
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception)
{
switch (logLevel)
{
case MqttNetLogLevel.Verbose:
LogMessage?.Log(LogLevel.Trace, source, message != null ? (parameters != null ? message != null ? (parameters != null ? string.Format(message, parameters) : message) : string.Empty : message) : string.Empty, exception);
break;
case MqttNetLogLevel.Info:
LogMessage?.Log(LogLevel.Information, source, message != null ? (parameters != null ? string.Format(message, parameters) : message) : string.Empty, exception);
break;
case MqttNetLogLevel.Warning:
LogMessage?.Log(LogLevel.Warning, source, message != null ? (parameters != null ? string.Format(message, parameters) : message) : string.Empty, exception);
break;
case MqttNetLogLevel.Error:
LogMessage?.Log(LogLevel.Warning, source, message != null ? (parameters != null ? string.Format(message, parameters) : message) : string.Empty, exception);
break;
}
}
}

View File

@@ -1,126 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
namespace ThingsGateway.Application;
/// <summary>
/// 上传设备运行状态
/// </summary>
public class UploadDeviceRunTime : UploadDevice
{
/// <summary>
/// 设备驱动名称
/// </summary>
[Description("设备驱动名称")]
public string PluginName { get; set; }
/// <summary>
/// 关联变量数量
/// </summary>
[Description("关联变量数量")]
public int UploadVariableCount { get; set; }
/// <summary>
/// 设备活跃时间
/// </summary>
[Description("活跃时间")]
public DateTime ActiveTime { get; private set; } = DateTime.MinValue;
/// <summary>
/// 设备状态
/// </summary>
[Description("设备状态")]
public DeviceStatusEnum DeviceStatus
{
get
{
if (KeepRun)
return deviceStatus;
else
return DeviceStatusEnum.Pause;
}
private set
{
if (deviceStatus != value)
{
deviceStatus = value;
}
}
}
private string lastErrorMessage;
/// <summary>
/// 最后一次失败原因
/// </summary>
[Description("最后一次失败原因")]
public string LastErrorMessage
{
get
{
return lastErrorMessage;
}
private set
{
lastErrorMessage = SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat() + " - " + value;
}
}
/// <summary>
/// 运行
/// </summary>
[Description("运行")]
public bool KeepRun { get; set; } = true;
private int errorCount;
/// <summary>
/// 距上次成功时的读取失败次数,超过3次设备更新为离线等于0时设备更新为在线
/// </summary>
[Description("失败次数")]
public int ErrorCount
{
get
{
return errorCount;
}
private set
{
errorCount = value;
if (errorCount > 3)
{
DeviceStatus = DeviceStatusEnum.OffLine;
}
else if (errorCount == 0)
{
DeviceStatus = DeviceStatusEnum.OnLine;
}
}
}
private DeviceStatusEnum deviceStatus = DeviceStatusEnum.None;
/// <summary>
/// 传入设备的状态信息
/// </summary>
/// <param name="activeTime"></param>
/// <param name="errorCount"></param>
/// <param name="lastErrorMessage"></param>
public void SetDeviceStatus(DateTime? activeTime = null, int? errorCount = null, string lastErrorMessage = null)
{
if (activeTime != null)
ActiveTime = activeTime.Value;
if (errorCount != null)
ErrorCount = errorCount.Value;
if (lastErrorMessage != null)
LastErrorMessage = lastErrorMessage;
}
}

View File

@@ -1,256 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.Serial;
namespace ThingsGateway.Application;
/// <summary>
/// <para></para>
/// 采集插件继承实现不同PLC通讯
/// <para></para>
/// 读取字符串DateTime等等不确定返回字节数量的方法属性特殊方法需使用<see cref="DeviceMethodAttribute"/>特性标识
/// </summary>
public abstract class CollectBase : DriverBase
{
/// <summary>
/// 当前采集设备
/// </summary>
public CollectDeviceRunTime CurDevice;
/// <summary>
/// 返回是否支持读取
/// </summary>
/// <returns></returns>
public abstract bool IsSupportRequest { get; }
/// <summary>
/// 一般底层驱动也有可能为null
/// </summary>
protected abstract IReadWriteDevice PLC { get; }
/// <summary>
/// 数据转换器
/// </summary>
/// <returns></returns>
public abstract IThingsGatewayBitConverter ThingsGatewayBitConverter { get; }
/// <summary>
/// 结束通讯后执行的方法
/// </summary>
/// <returns></returns>
public abstract Task AfterStopAsync();
/// <summary>
/// 开始通讯前执行的方法
/// </summary>
/// <returns></returns>
public abstract Task BeforStartAsync(CancellationToken token);
/// <summary>
/// 通道标识
/// </summary>
public virtual OperResult<string> GetChannelID()
{
var config = (CollectDriverPropertyBase)DriverPropertys;
if (config.IsShareChannel)
{
switch (config.ShareChannel)
{
case ShareChannelEnum.SerialPort:
return OperResult.CreateSuccessResult(config.PortName);
case ShareChannelEnum.TcpClientEx:
case ShareChannelEnum.UdpSession:
var a = new IPHost($"{config.IP}:{config.Port}");
return OperResult.CreateSuccessResult(config.ShareChannel.ToString() + a.ToString());
}
}
return new("不支持共享通道");
}
/// <summary>
/// 共享通道类型
/// </summary>
public virtual OperResult<object> GetShareChannel()
{
var config = (CollectDriverPropertyBase)DriverPropertys;
if (config.IsShareChannel)
{
switch (config.ShareChannel)
{
case ShareChannelEnum.None:
return new OperResult<object>("不支持共享链路");
case ShareChannelEnum.SerialPort:
var data = new SerialProperty()
{
PortName = config.PortName,
BaudRate = config.BaudRate,
DataBits = config.DataBits,
Parity = config.Parity,
StopBits = config.StopBits,
};
FoundataionConfig.SetValue(SerialConfigExtension.SerialProperty, data);
var serialSession = new SerialsSession();
(serialSession).Setup(FoundataionConfig);
return OperResult.CreateSuccessResult((object)serialSession);
case ShareChannelEnum.TcpClientEx:
FoundataionConfig.SetRemoteIPHost(new IPHost($"{config.IP}:{config.Port}"));
var tcpClient = new TcpClientEx();
(tcpClient).Setup(FoundataionConfig);
return OperResult.CreateSuccessResult((object)tcpClient);
case ShareChannelEnum.UdpSession:
FoundataionConfig.SetRemoteIPHost(new IPHost($"{config.IP}:{config.Port}"));
var udpSession = new UdpSession();
return OperResult.CreateSuccessResult((object)udpSession);
}
}
return new OperResult<object>("不支持共享链路");
}
/// <summary>
/// 初始化
/// </summary>
public void Init(ILogger logger, CollectDeviceRunTime device, object client = null)
{
_logger = logger;
IsLogOut = device.IsLogOut;
CurDevice = device;
Init(device, client);
}
/// <summary>
/// 共享链路需重新设置适配器时调用该方法
/// </summary>
public abstract void InitDataAdapter();
/// <summary>
/// 连读打包,返回实际通讯包信息<see cref="DeviceVariableSourceRead"/>
/// <br></br>每个驱动打包方法不一样,所以需要实现这个接口
/// </summary>
/// <param name="deviceVariables">设备下的全部通讯点位</param>
/// <returns></returns>
public abstract List<DeviceVariableSourceRead> LoadSourceRead(List<DeviceVariableRunTime> deviceVariables);
/// <summary>
/// 采集驱动读取
/// </summary>
public virtual async Task<OperResult<byte[]>> ReadSourceAsync(DeviceVariableSourceRead deviceVariableSourceRead, CancellationToken token)
{
if (IsSupportRequest)
{
OperResult<byte[]> read = await ReadAsync(deviceVariableSourceRead.Address, deviceVariableSourceRead.Length, token);
if (read == null || !read.IsSuccess)
{
deviceVariableSourceRead.DeviceVariables.ForEach(it =>
{
var operResult = it.SetValue(null, isOnline: false);
if (!operResult.IsSuccess)
{
_logger.LogWarning("变量值更新失败:" + operResult.Message);
}
});
return read;
}
else
{
return ReadWriteHelpers.DealWithReadResult(read, content =>
{
ReadWriteHelpers.PraseStructContent(content, deviceVariableSourceRead.DeviceVariables);
});
}
}
else
{
return new OperResult<byte[]>("不支持默认读取方式");
}
}
/// <summary>
/// 批量写入变量值,需返回变量名称/结果
/// </summary>
/// <returns></returns>
public virtual async Task<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<DeviceVariableRunTime, JToken> writeInfoLists, CancellationToken token)
{
if (PLC == null)
throw new("未初始化成功");
Dictionary<string, OperResult> operResults = new();
foreach (var writeInfo in writeInfoLists)
{
var result = await PLC.WriteAsync(writeInfo.Key.VariableAddress, writeInfo.Key.DataType, writeInfo.Value.ToString(), token);
await Task.Delay(10, token); //防止密集写入
operResults.Add(writeInfo.Key.Name, result);
}
return operResults;
}
/// <summary>
/// 初始化
/// </summary>
/// <param name="device">设备</param>
/// <param name="client">链路对象如TCPClient</param>
protected abstract void Init(CollectDeviceRunTime device, object client = null);
/// <summary>
/// 底层日志输出
/// </summary>
protected override void Log_Out(TouchSocket.Core.LogLevel arg1, object arg2, string arg3, Exception arg4)
{
if (arg1 >= TouchSocket.Core.LogLevel.Warning)
{
CurDevice.SetDeviceStatus(lastErrorMessage: arg3);
}
if (IsLogOut || arg1 >= TouchSocket.Core.LogLevel.Warning)
{
_logger.Log_Out(arg1, arg2, arg3, arg4);
}
}
internal override void NewMessage(TouchSocket.Core.LogLevel arg1, object arg2, string arg3, Exception arg4)
{
if (IsSaveLog)
{
if (arg3.StartsWith(FoundationConst.LogMessageHeader))
{
var customLevel = App.GetConfig<Microsoft.Extensions.Logging.LogLevel?>("Logging:LogLevel:BackendLog") ?? Microsoft.Extensions.Logging.LogLevel.Trace;
if ((byte)arg1 < (byte)customLevel)
{
var logRuntime = new BackendLog
{
LogLevel = (Microsoft.Extensions.Logging.LogLevel)arg1,
LogMessage = arg3,
LogSource = "采集设备:" + CurDevice.Name,
LogTime = SysDateTimeExtensions.CurrentDateTime,
Exception = null,
};
_logQueues.Enqueue(logRuntime);
}
}
}
base.NewMessage(arg1, arg2, arg3, arg4);
}
/// <summary>
/// 返回全部内容字节数组
/// <br></br>
/// 通常使用<see cref="IReadWrite.ReadAsync(string, int, CancellationToken)"/>可以直接返回正确信息
/// </summary>
protected abstract Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken token);
}

View File

@@ -1,150 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
using TouchSocket.Core;
namespace ThingsGateway.Application;
/// <summary>
/// 插件基类,注意继承的插件的命名空间需要符合<see cref="ExportHelpers.PluginLeftName"/>前置名称
/// </summary>
public abstract class DriverBase : DisposableObject
{
/// <summary>
/// <inheritdoc cref="TouchSocket.Core.TouchSocketConfig"/>
/// </summary>
public TouchSocketConfig FoundataionConfig;
/// <summary>
/// 日志
/// </summary>
internal ILogger _logger;
/// <inheritdoc cref="DriverBase"/>
public DriverBase()
{
FoundataionConfig = new TouchSocketConfig();
LogMessage = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
LogMessage.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
FoundataionConfig.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
Task.Factory.StartNew(LogInsertAsync);
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
FoundataionConfig.Dispose();
base.Dispose(disposing);
}
/// <summary>
/// 调试UI Type如果不存在返回null
/// </summary>
public abstract Type DriverDebugUIType { get; }
/// <summary>
/// 当前插件描述
/// </summary>
public DriverPlugin DriverPlugin { get; internal set; }
/// <summary>
/// 插件配置项 ,继承实现<see cref="DriverPropertyBase"/>后返回继承类如果不存在返回null
/// </summary>
public abstract DriverPropertyBase DriverPropertys { get; }
/// <summary>
/// 是否输出日志
/// </summary>
public bool IsLogOut { get; set; }
/// <summary>
/// 是否存储报文
/// </summary>
public bool IsSaveLog { get; set; }
/// <summary>
/// 报文信息
/// </summary>
public ConcurrentLinkedList<string> Messages { get; set; } = new();
/// <summary>
/// 底层日志,如果需要在Blazor界面中显示报文日志需要输出字符串头部为<see cref="FoundationConst.LogMessageHeader"/>的日志
/// </summary>
protected internal LoggerGroup LogMessage { get; private set; }
/// <summary>
/// 是否连接成功,如果是上传设备,会直接影响到上传设备的运行状态,如果是采集设备并且不支持读取,需要自更新在线状态
/// </summary>
/// <returns></returns>
public abstract bool IsConnected();
/// <summary>
/// 存储日志队列
/// </summary>
protected ConcurrentQueue<BackendLog> _logQueues = new();
/// <summary>
/// 设备报文
/// </summary>
internal virtual void NewMessage(TouchSocket.Core.LogLevel arg1, object arg2, string arg3, Exception arg4)
{
if (IsLogOut)
{
if (arg3.StartsWith(FoundationConst.LogMessageHeader))
{
Messages.Add(SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat() + " - " + arg3.Substring(0, Math.Min(arg3.Length, 200)));
if (Messages.Count > 2500)
{
Messages.Clear();
}
}
}
}
private async Task LogInsertAsync()
{
var db = DbContext.Db.CopyNew();
while (!DisposedValue)
{
if (_logQueues.Count > 0)
{
try
{
var data = _logQueues.ToListWithDequeue();
await db.InsertableWithAttr(data).ExecuteCommandAsync();//入库
}
catch
{
}
}
await Task.Delay(5000);
}
}
/// <summary>
/// 底层日志输出
/// </summary>
protected virtual void Log_Out(TouchSocket.Core.LogLevel arg1, object arg2, string arg3, Exception arg4)
{
if (IsLogOut || arg1 >= TouchSocket.Core.LogLevel.Warning)
{
_logger.Log_Out(arg1, arg2, arg3, arg4);
}
}
}

View File

@@ -1,324 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Furion.FriendlyException;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Reflection;
using System.Runtime.Loader;
using ThingsGateway.Application.Extensions;
using Yitter.IdGenerator;
namespace ThingsGateway.Application;
/// <summary>
/// 驱动插件服务
/// </summary>
public class PluginSingletonService : ISingleton
{
private readonly ILogger<PluginSingletonService> _logger;
/// <inheritdoc cref="PluginSingletonService"/>
public PluginSingletonService(ILogger<PluginSingletonService> logger)
{
_logger = logger;
}
/// <summary>
/// 插件文件路径/插件程序集
/// </summary>
public ConcurrentDictionary<string, Assembly> AssemblyDict { get; private set; } = new();
/// <summary>
/// 插件文件路径/插件域
/// </summary>
public ConcurrentDictionary<string, AssemblyLoadContext> AssemblyLoadContextDict { get; private set; } = new();
/// <summary>
/// 插件ID/插件Type
/// </summary>
public ConcurrentDictionary<long, Type> DriverPluginDict { get; private set; } = new();
/// <summary>
/// 获取插件
/// </summary>
/// <param name="plugin"></param>
/// <returns></returns>
public DriverBase GetDriver(DriverPlugin plugin)
{
lock (this)
{
//先判断是否已经拥有插件模块
if (DriverPluginDict.ContainsKey(plugin.Id))
{
var driver = (DriverBase)Activator.CreateInstance(DriverPluginDict[plugin.Id]);
driver.DriverPlugin = plugin;
return driver;
}
Assembly assembly = null;
_logger?.LogInformation($"添加插件文件:{plugin.FilePath}");
//根据路径获取dll文件
//主程序集路径
var path = AppContext.BaseDirectory.CombinePathOS(plugin.FilePath);
//全部程序集路径
List<string> paths = new();
Directory.GetFiles(Path.GetDirectoryName(path), "*.dll").ToList().
ForEach(a => paths.Add(a.Replace("\\", "/")));
if (AssemblyDict.ContainsKey(plugin.FilePath))
{
assembly = AssemblyDict[plugin.FilePath];
}
else
{
//新建插件域,并注明不可卸载
var assemblyLoadContext = new AssemblyLoadContext(plugin.Id.ToString(), false);
//获取插件程序集
assembly = GetAssembly(path, paths, assemblyLoadContext);
//添加到全局对象
AssemblyLoadContextDict.TryAdd(plugin.FilePath, assemblyLoadContext);
AssemblyDict.TryAdd(plugin.FilePath, assembly);
}
if (assembly != null)
{
//根据采集/上传类型获取实际插件类
switch (plugin.DriverTypeEnum)
{
case DriverEnum.Collect:
var driverType = assembly.GetTypes().Where(x => (typeof(CollectBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).FirstOrDefault(it => it.Name == plugin.AssembleName);
if (driverType != null)
{
return GetDriver(plugin, driverType);
}
break;
case DriverEnum.Upload:
var upLoadType = assembly.GetTypes().Where(x => (typeof(UpLoadBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).FirstOrDefault(it => it.Name == plugin.AssembleName);
if (upLoadType != null)
{
return GetDriver(plugin, upLoadType);
}
break;
}
throw new Exception($"加载插件 {plugin.FilePath}-{plugin.AssembleName} 失败,{plugin.AssembleName}不存在");
}
else
{
throw new Exception($"加载驱动插件 {path} 失败,文件不存在");
}
DriverBase GetDriver(DriverPlugin plugin, Type driverType)
{
var driver = (DriverBase)Activator.CreateInstance(driverType);
_logger?.LogInformation($"加载插件 {plugin.FilePath}-{plugin.AssembleName} 成功");
DriverPluginDict.TryAdd(plugin.Id, driverType);
driver.DriverPlugin = plugin;
return driver;
}
Assembly GetAssembly(string path, List<string> paths, AssemblyLoadContext assemblyLoadContext)
{
Assembly assembly = null;
foreach (var item in paths)
{
using var fs = new FileStream(item, FileMode.Open);
if (item == path)
assembly = assemblyLoadContext.LoadFromStream(fs);
else
{
try
{
assemblyLoadContext.LoadFromStream(fs);
}
catch (Exception ex)
{
_logger.LogWarning($"尝试加载附属程序集{item}失败如果此程序集为非引用比如非托管DllImport可以忽略此警告。错误信息{(ex.Message)}");
}
}
}
return assembly;
}
}
}
/// <summary>
/// 获取插件的属性值
/// </summary>
public List<DependencyProperty> GetDriverProperties(DriverBase driver)
{
var data = driver.DriverPropertys?.GetType().GetPropertiesWithCache().SelectMany(it =>
new[] { new { property = it, devicePropertyAttribute = it.GetCustomAttribute<DevicePropertyAttribute>() } })
.Where(x => x.devicePropertyAttribute != null).ToList()
.SelectMany(it => new[]
{
new DependencyProperty(){
PropertyName=it.property.Name,
Description=it.devicePropertyAttribute.Description,
Remark=it.devicePropertyAttribute.Remark,
Value=it.property.GetValue(driver.DriverPropertys)?.ToString(),
}
});
return data.ToList();
}
/// <summary>
/// 获取插件的变量上传属性值
/// </summary>
public List<DependencyProperty> GetDriverVariableProperties(UpLoadBase driver)
{
var data = driver.VariablePropertys?.GetType().GetPropertiesWithCache()?.SelectMany(it =>
new[] { new { property = it, devicePropertyAttribute = it.GetCustomAttribute<VariablePropertyAttribute>() } })
?.Where(x => x.devicePropertyAttribute != null).ToList()
?.SelectMany(it => new[]
{
new DependencyProperty(){
PropertyName=it.property.Name,
Description=it.devicePropertyAttribute.Description,
Remark=it.devicePropertyAttribute.Remark,
Value=it.property.GetValue(driver.VariablePropertys)?.ToString(),
}
});
return data?.ToList();
}
/// <summary>
/// 获取插件方法
/// </summary>
/// <param name="driver"></param>
/// <returns></returns>
public List<MethodInfo> GetMethod(DriverBase driver)
{
return driver.GetType().GetMethods().Where(
x => x.GetCustomAttribute(typeof(DeviceMethodAttribute)) != null).ToList();
}
/// <summary>
/// 设置插件的属性值
/// </summary>
public void SetDriverProperties(DriverBase driver, List<DependencyProperty> deviceProperties)
{
var pluginPropertys = driver.DriverPropertys?.GetType().GetPropertiesWithCache().Where(a => a.GetCustomAttribute(typeof(DevicePropertyAttribute)) != null)?.ToList();
foreach (var propertyInfo in pluginPropertys ?? new())
{
var deviceProperty = deviceProperties.FirstOrDefault(x => x.PropertyName == propertyInfo.Name);
if (deviceProperty == null) continue;
var value = ReadWriteHelpers.ObjToTypeValue(propertyInfo, deviceProperty?.Value ?? "");
propertyInfo.SetValue(driver.DriverPropertys, value);
}
}
/// <summary>
/// 尝试添加插件,返回插件表示类,方法完成后会完全卸载插件
/// </summary>
/// <param name="plugin"></param>
/// <returns></returns>
public async Task<List<DriverPlugin>> TestAddDriverAsync(DriverPluginAddInput plugin)
{
var assemblyLoadContext = new AssemblyLoadContext(YitIdHelper.NextId().ToString(), true);
try
{
var driverPlugins = new List<DriverPlugin>();
var maxFileSize = 100 * 1024 * 1024;//最大100m
//主程序集名称
var mainFileName = Path.GetFileNameWithoutExtension(plugin.MainFile.Name);
//插件文件夹相对路径
var dir = "Plugins".CombinePathOS(mainFileName);
//插件文件夹绝对路径
var fullDir = AppContext.BaseDirectory.CombinePathOS(dir);
//主程序集相对路径
var path = dir.CombinePathOS(plugin.MainFile.Name);
//主程序集绝对路径
var fullPath = fullDir.CombinePathOS(plugin.MainFile.Name);
//主程序集相对路径
//获取文件流
using var stream = plugin.MainFile.OpenReadStream(maxFileSize);
Directory.CreateDirectory(fullDir);//创建插件文件夹
using FileStream fs = new(fullPath, FileMode.Create);
await stream.CopyToAsync(fs);
fs.Seek(0, SeekOrigin.Begin);
//获取主程序集
var assembly = assemblyLoadContext.LoadFromStream(fs);
foreach (var item in plugin.OtherFiles)
{
using var otherStream = item.OpenReadStream(maxFileSize);
using FileStream fs1 = new(fullDir.CombinePathOS(item.Name), FileMode.Create);
await otherStream.CopyToAsync(fs1);
fs1.Seek(0, SeekOrigin.Begin);
try
{
assemblyLoadContext.LoadFromStream(fs1);
}
catch (Exception ex)
{
_logger.LogWarning($"尝试加载附属程序集{item}失败如果此程序集为非引用比如非托管DllImport可以忽略此警告。错误信息{(ex.Message)}");
}
}
if (assembly != null)
{
//获取插件的相关信息
var collectBase = assembly.GetTypes().Where(x => (typeof(CollectBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).ToList();
for (int i = 0; i < collectBase.Count; i++)
{
var item = collectBase[i];
driverPlugins.Add(new DriverPlugin()
{
AssembleName = item.Name,
DriverTypeEnum = DriverEnum.Collect,
FilePath = path,
FileName = mainFileName,
});
}
collectBase.ForEach(a => a = null);
collectBase.Clear();
collectBase = null;
var upLoadBase = assembly.GetTypes().Where(x => (typeof(UpLoadBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).ToList();
for (int i = 0; i < upLoadBase.Count; i++)
{
var item = upLoadBase[i];
driverPlugins.Add(new DriverPlugin()
{
AssembleName = item.Name,
DriverTypeEnum = DriverEnum.Upload,
FilePath = path,
FileName = mainFileName,
});
}
upLoadBase.ForEach(a => a = null);
upLoadBase.Clear();
upLoadBase = null;
}
else
{
throw Oops.Bah("加载驱动文件失败");
}
if (driverPlugins.Count == 0)
{
throw Oops.Bah("找不到对应的驱动");
}
assembly = null;
return driverPlugins;
}
finally
{
assemblyLoadContext.Unload();
}
}
}

View File

@@ -1,186 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Microsoft.Extensions.Logging;
using ThingsGateway.Foundation;
using TouchSocket.Core;
namespace ThingsGateway.Application;
/// <summary>
/// 上传插件
/// <para></para>
/// 约定:
/// <para></para>
/// 如果设备属性需要密码输入属性名称中需包含Password字符串
/// <para></para>
/// 如果设备属性需要大文本输入属性名称中需包含BigText字符串
/// <br></br>
/// 因为自定义上传插件需求比较大,这里着重解释代码运行原理
/// 继承<see cref="UpLoadBase"/>后,可以看到需要实现各类虚方法/属性<br></br>
/// <see cref="UploadVariables"/> <br></br>
/// <see cref="VariablePropertys"/><br></br>
/// <see cref="DriverBase.DriverPropertys"/><br></br>
/// <see cref="BeforStartAsync"/><br></br>
/// <see cref="ExecuteAsync"/><br></br>
/// <see cref="DriverBase.IsConnected"/><br></br>
/// <see cref="Init(UploadDeviceRunTime)"/><br></br>
/// 含义可看注释,下面看看网关上传插件的生命周期<br></br>
/// 1、构造函数<see cref="UpLoadBase()"/> 传入参数服务工厂,在需要获取服务时使用<see cref="App.GetService{TService}(IServiceProvider)"/><br></br>
/// 2、<see cref="Init(UploadDeviceRunTime)"/>初始化函数传入上传设备参数只执行一次在这个方法内一般会初始化一些必要的实例比如new MqttClient以及一些必要的实现属性比如<see cref="UploadVariables"/><br></br>
/// 3、<see cref="BeforStartAsync"/>开始前执行的方法比如连接mqtt等只执行一次<br></br>
/// 4、<see cref="ExecuteAsync"/>核心执行的方法,需实现上传方法,在插件结束前会一直循环调用<br></br>
/// 5、<see cref="DisposableObject.Dispose(bool)"/> 结束时调用的方法,实现资源释放方法<br></br>
/// 网关的数据是如何传入到上传插件的下面会以Mqtt上传为例<br></br>
/// 1、如何获取采集变量值在初始化函数中<see cref="Init(UploadDeviceRunTime)"/>获取全局设备/变量<br></br>
/// 通过<see cref="App.GetService{TService}(IServiceProvider)"/>获取单例服务<see cref="GlobalDeviceData"/><br></br>
/// 可以看到在这个单例服务中,已经拥有全部的采集设备与变量<br></br>
/// 2、如何获取采集变量中的上传属性UploadBase中封装了通用方法<see cref="GetPropertyValue(DeviceVariableRunTime, string)"/><br></br>
/// 比如定义了变量属性Enable只有设置为true的变量才会用作某逻辑执行方法GetPropertyValue(tag,"Enable")也可用硬编码传入propertyName参数<br></br>
/// 3、如何定义自己的上传实体第一步中获取获取单例服务<see cref="GlobalDeviceData"/>,在拥有全局变量下,可以使用<see cref="Mapster"/> 或者 手动赋值到DTO实体<br></br>
/// 4、完整的参考可以查看MqttClient插件ThingsGateway\src\Plugins\ThingsGateway.Mqtt\ThingsGateway.Mqtt.csproj<br></br>
/// </summary>
public abstract class UpLoadBase : DriverBase
{
/// <summary>
/// 当前上传设备
/// </summary>
public UploadDeviceRunTime CurDevice { get; protected set; }
/// <summary>
/// 返回插件的上传变量,一般在<see cref="Init(UploadDeviceRunTime)"/>后初始化
/// </summary>
public abstract List<DeviceVariableRunTime> UploadVariables { get; }
/// <summary>
/// 插件配置项 ,继承实现<see cref="VariablePropertyBase"/>后返回继承类如果不存在返回null
/// </summary>
public abstract VariablePropertyBase VariablePropertys { get; }
/// <summary>
/// 离线缓存
/// </summary>
protected CacheDb CacheDb { get; set; }
/// <summary>
/// 结束通讯后执行的方法
/// </summary>
/// <returns></returns>
public abstract Task AfterStopAsync();
/// <summary>
/// 开始执行的方法
/// </summary>
/// <returns></returns>
public abstract Task BeforStartAsync(CancellationToken token);
/// <summary>
/// 循环执行
/// </summary>
public abstract Task ExecuteAsync(CancellationToken token);
/// <summary>
/// 获取设备的属性值
/// </summary>
public virtual string GetDevicePropertyValue(CollectDeviceRunTime collectDeviceRunTime, string propertyName)
{
if (collectDeviceRunTime == null)
return null;
return collectDeviceRunTime.DevicePropertys.FirstOrDefault(a => a.PropertyName == propertyName).Value;
}
/// <summary>
/// 获取变量的属性值
/// </summary>
public virtual string GetPropertyValue(DeviceVariableRunTime variableRunTime, string propertyName)
{
if (variableRunTime == null)
return null;
if (variableRunTime.VariablePropertys.ContainsKey(CurDevice.Id))
{
var data = variableRunTime.VariablePropertys[CurDevice.Id].FirstOrDefault(a =>
a.PropertyName == propertyName);
if (data != null)
{
return data.Value;
}
}
return null;
}
/// <summary>
/// 初始化
/// </summary>
public void Init(ILogger logger, UploadDeviceRunTime device)
{
_logger = logger;
IsLogOut = device.IsLogOut;
CurDevice = device;
CacheDb = new(CurDevice.Id.ToString());
Init(device);
}
/// <summary>
/// 初始化
/// </summary>
/// <param name="device">设备</param>
protected abstract void Init(UploadDeviceRunTime device);
/// <summary>
/// 底层日志输出
/// </summary>
protected override void Log_Out(TouchSocket.Core.LogLevel arg1, object arg2, string arg3, Exception arg4)
{
if (arg1 >= TouchSocket.Core.LogLevel.Warning)
{
CurDevice.SetDeviceStatus(lastErrorMessage: arg3);
}
if (IsLogOut || arg1 >= TouchSocket.Core.LogLevel.Warning)
{
_logger.Log_Out(arg1, arg2, arg3, arg4);
}
}
internal override void NewMessage(TouchSocket.Core.LogLevel arg1, object arg2, string arg3, Exception arg4)
{
if (IsSaveLog)
{
if (arg3.StartsWith(FoundationConst.LogMessageHeader))
{
var customLevel = App.GetConfig<Microsoft.Extensions.Logging.LogLevel?>("Logging:LogLevel:BackendLog") ?? Microsoft.Extensions.Logging.LogLevel.Trace;
if ((byte)arg1 < (byte)customLevel)
{
var logRuntime = new BackendLog
{
LogLevel = (Microsoft.Extensions.Logging.LogLevel)arg1,
LogMessage = arg3,
LogSource = "上传设备:" + CurDevice.Name,
LogTime = SysDateTimeExtensions.CurrentDateTime,
Exception = null,
};
_logQueues.Enqueue(logRuntime);
}
}
}
base.NewMessage(arg1, arg2, arg3, arg4);
}
}

View File

@@ -1,237 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using System.Collections.Concurrent;
using ThingsGateway.Admin.Core.JsonExtensions;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
namespace ThingsGateway.Application;
/// <summary>
/// 变量写入/执行变量附带方法,单例服务
/// </summary>
public class RpcSingletonService : ISingleton
{
/// <summary>
/// 写入变量说明
/// </summary>
public const string WriteVariable = "写入变量";
private readonly ILogger<RpcSingletonService> _logger;
private readonly CollectDeviceWorker _collectDeviceHostService;
private readonly GlobalDeviceData _globalDeviceData;
private readonly ConcurrentQueue<RpcLog> _logQueues = new();
/// <inheritdoc cref="RpcSingletonService"/>
public RpcSingletonService(ILogger<RpcSingletonService> logger)
{
_logger = logger;
_globalDeviceData = ServiceHelper.Services.GetService<GlobalDeviceData>();
_collectDeviceHostService = ServiceHelper.GetBackgroundService<CollectDeviceWorker>();
Task.Factory.StartNew(RpcLogInsertAsync, TaskCreationOptions.LongRunning);
}
/// <summary>
/// 反向RPC入口方法
/// </summary>
/// <param name="sourceDes">触发该方法的源说明</param>
/// <param name="items">指定键为变量名称,值为附带方法参数或写入值</param>
/// <param name="isBlazor">如果是true不检查<see cref="MemoryVariable.RpcWriteEnable"/>字段</param>
/// <param name="token"><see cref="CancellationToken"/> 取消源</param>
/// <returns></returns>
public async Task<Dictionary<string, OperResult>> InvokeDeviceMethodAsync(string sourceDes, Dictionary<string, string> items, bool isBlazor = false, CancellationToken token = default)
{
Dictionary<CollectDeviceCore, Dictionary<DeviceVariableRunTime, JToken>> WriteVariables = new();
Dictionary<CollectDeviceCore, Dictionary<DeviceVariableRunTime, string>> WriteMethods = new();
Dictionary<string, OperResult> results = new();
foreach (var item in items)
{
OperResult data = new();
var tag = _globalDeviceData.AllVariables.FirstOrDefault(it => it.Name == item.Key);
if (tag == null)
results.Add(item.Key, new("不存在变量:" + item.Key));
if (tag.ProtectTypeEnum == ProtectTypeEnum.ReadOnly)
results.Add(item.Key, new("只读变量:" + item.Key));
if (!tag.RpcWriteEnable && !isBlazor)
results.Add(item.Key, new("不允许远程写入:" + item.Key));
if (tag.IsMemoryVariable == true)
{
results.Add(item.Key, tag.SetValue(item.Value));
}
var dev = _collectDeviceHostService.CollectDeviceCores.FirstOrDefault(it => it.Device.Id == tag.DeviceId);
if (dev == null)
results.Add(item.Key, new OperResult("系统错误,不存在对应采集设备,请稍候重试"));
if (dev.Device.DeviceStatus == DeviceStatusEnum.OffLine)
results.Add(item.Key, new OperResult("设备已离线"));
if (dev.Device.DeviceStatus == DeviceStatusEnum.Pause)
results.Add(item.Key, new OperResult("设备已暂停"));
if (!results.ContainsKey(item.Key))
{
if (string.IsNullOrEmpty(tag.OtherMethod))
{
//写入变量
JToken tagValue;
try
{
tagValue = JToken.Parse(item.Value);
}
catch (Exception)
{
tagValue = JToken.Parse("\"" + item.Value + "\"");
}
if (WriteVariables.ContainsKey(dev))
{
WriteVariables[dev].Add(tag, tagValue);
}
else
{
WriteVariables.Add(dev, new());
WriteVariables[dev].Add(tag, tagValue);
}
}
else
{
if (WriteMethods.ContainsKey(dev))
{
WriteMethods[dev].Add(tag, item.Value);
}
else
{
WriteVariables.Add(dev, new());
WriteVariables[dev].Add(tag, item.Value);
}
}
}
}
foreach (var item in WriteVariables)
{
try
{
var result = await item.Key.InVokeWriteAsync(item.Value, token);
foreach (var resultItem in result)
{
string operObj;
string parJson;
if (resultItem.Key.IsNullOrEmpty())
{
operObj = items.Select(x => x.Key).ToJsonString();
parJson = items.Select(x => x.Value).ToJsonString();
}
else
{
operObj = resultItem.Key;
parJson = items[resultItem.Key];
}
_logQueues.Enqueue(
new RpcLog()
{
LogTime = SysDateTimeExtensions.CurrentDateTime,
OperateMessage = resultItem.Value.Exception,
IsSuccess = resultItem.Value.IsSuccess,
OperateMethod = WriteVariable,
OperateObject = operObj,
OperateSource = sourceDes,
ParamJson = parJson,
ResultJson = resultItem.Value.Message
}
);
if (!resultItem.Value.IsSuccess)
{
_logger.LogWarning($"写入变量[{resultItem.Key}]失败:{resultItem.Value.Message}");
}
}
results.AddRange(result);
}
catch (Exception ex)
{
_logger.LogWarning($"写入变量异常:{ex.Message + Environment.NewLine + ex.StackTrace}");
}
}
foreach (var item in WriteMethods)
{
foreach (var writeMethod in item.Value)
{
//执行变量附带的方法
var method = item.Key.DeviceVariableMethodSources.FirstOrDefault(it => it.DeviceVariable == writeMethod.Key);
OperResult<string> result;
try
{
result = await item.Key.InvokeMethodAsync(method, false, writeMethod.Value, token);
results.Add(writeMethod.Key.Name, result);
}
catch (Exception ex)
{
result = new OperResult<string>(ex);
results.Add(writeMethod.Key.Name, result);
}
_logQueues.Enqueue(
new RpcLog()
{
LogTime = SysDateTimeExtensions.CurrentDateTime,
OperateMessage = result.Exception,
IsSuccess = result.IsSuccess,
OperateMethod = writeMethod.Key.OtherMethod,
OperateObject = writeMethod.Key.Name,
OperateSource = sourceDes,
ParamJson = writeMethod.Value?.ToString(),
ResultJson = result.Message
}
);
if (!result.IsSuccess)
{
_logger.LogWarning($"执行变量[{writeMethod.Key.Name}]方法[{writeMethod.Key.OtherMethod}]失败:{result.Message}");
}
}
}
return results;
}
private async Task RpcLogInsertAsync()
{
var db = DbContext.Db.CopyNew();
while (true)
{
try
{
var data = _logQueues.ToListWithDequeue();
db.InsertableWithAttr(data).ExecuteCommand();//入库
}
catch
{
}
await Task.Delay(3000);
}
}
}

View File

@@ -1,30 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using ThingsGateway.Admin.Application;
namespace ThingsGateway.Application;
/// <summary>
/// 系统配置种子数据
/// </summary>
public class DriverPluginSeedData : ISqlSugarEntitySeedData<DriverPlugin>
{
/// <inheritdoc/>
public IEnumerable<DriverPlugin> SeedData()
{
return SeedDataUtil.GetSeedData<DriverPlugin>("driver_plugin.json")
.Concat(SeedDataUtil.GetSeedData<DriverPlugin>("pro_driver_plugin.json"))
.Concat(SeedDataUtil.GetSeedData<DriverPlugin>("custom_driver_plugin.json"))
;
}
}

View File

@@ -1,357 +0,0 @@
{
"RECORDS": [
{
"Id": 319003388334342,
"FileName": "ThingsGateway.Modbus",
"AssembleName": "ModbusRtu",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.dll",
"CreateTime": "2023/2/23 15:18:52",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": "2023/3/2 12:40:28",
"UpdateUser": "superAdmin",
"UpdateUserId": 212725263002001,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 389003388313861,
"FileName": "ThingsGateway.Modbus",
"AssembleName": "ModbusRtuOverTcp",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.dll",
"CreateTime": "2023/2/23 15:18:52",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": "2023/3/2 12:40:28",
"UpdateUser": "superAdmin",
"UpdateUserId": 212725263002001,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 389003388334341,
"FileName": "ThingsGateway.Modbus",
"AssembleName": "ModbusRtuOverUdp",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.dll",
"CreateTime": "2023/2/23 15:18:52",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": "2023/3/2 12:40:28",
"UpdateUser": "superAdmin",
"UpdateUserId": 212725263002001,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 389003388334342,
"FileName": "ThingsGateway.Modbus",
"AssembleName": "ModbusTcp",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.dll",
"CreateTime": "2023/2/23 15:18:52",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": "2023/3/2 12:40:28",
"UpdateUser": "superAdmin",
"UpdateUserId": 212725263002001,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 389003388334343,
"FileName": "ThingsGateway.Modbus",
"AssembleName": "ModbusUdp",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.dll",
"CreateTime": "2023/2/23 15:18:52",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": "2023/3/2 12:40:28",
"UpdateUser": "superAdmin",
"UpdateUserId": 212725263002001,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 391441718112517,
"FileName": "ThingsGateway.Modbus",
"AssembleName": "ModbusServer",
"DriverTypeEnum": "Upload",
"FilePath": "Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.dll",
"CreateTime": "2023/3/2 12:40:28",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087020826885,
"FileName": "ThingsGateway.OPCUA",
"AssembleName": "OPCUAClient",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.OPCUA/ThingsGateway.OPCUA.dll",
"CreateTime": "2023/8/6 18:21:47",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087020826886,
"FileName": "ThingsGateway.OPCUA",
"AssembleName": "OPCUAServer",
"DriverTypeEnum": "Upload",
"FilePath": "Plugins/ThingsGateway.OPCUA/ThingsGateway.OPCUA.dll",
"CreateTime": "2023/8/6 18:21:47",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087111926021,
"FileName": "ThingsGateway.OPCDA",
"AssembleName": "OPCDAClient",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.OPCDA/ThingsGateway.OPCDA.dll",
"CreateTime": "2023/8/6 18:22:09",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087153508613,
"FileName": "ThingsGateway.RabbitMQ",
"AssembleName": "RabbitMQClient",
"DriverTypeEnum": "Upload",
"FilePath": "Plugins/ThingsGateway.RabbitMQ/ThingsGateway.RabbitMQ.dll",
"CreateTime": "2023/8/6 18:22:19",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087210762501,
"FileName": "ThingsGateway.Siemens",
"AssembleName": "S7_1200",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.dll",
"CreateTime": "2023/8/6 18:22:33",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087210762502,
"FileName": "ThingsGateway.Siemens",
"AssembleName": "S7_1500",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.dll",
"CreateTime": "2023/8/6 18:22:33",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087210762503,
"FileName": "ThingsGateway.Siemens",
"AssembleName": "S7_200",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.dll",
"CreateTime": "2023/8/6 18:22:33",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087210762504,
"FileName": "ThingsGateway.Siemens",
"AssembleName": "S7_200SMART",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.dll",
"CreateTime": "2023/8/6 18:22:33",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087210762505,
"FileName": "ThingsGateway.Siemens",
"AssembleName": "S7_300",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.dll",
"CreateTime": "2023/8/6 18:22:33",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087210762506,
"FileName": "ThingsGateway.Siemens",
"AssembleName": "S7_400",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.dll",
"CreateTime": "2023/8/6 18:22:33",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087273050373,
"FileName": "ThingsGateway.Mqtt",
"AssembleName": "IotSharpClient",
"DriverTypeEnum": "Upload",
"FilePath": "Plugins/ThingsGateway.Mqtt/ThingsGateway.Mqtt.dll",
"CreateTime": "2023/8/6 18:22:48",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087273050374,
"FileName": "ThingsGateway.Mqtt",
"AssembleName": "MqttClient",
"DriverTypeEnum": "Upload",
"FilePath": "Plugins/ThingsGateway.Mqtt/ThingsGateway.Mqtt.dll",
"CreateTime": "2023/8/6 18:22:48",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087273050375,
"FileName": "ThingsGateway.Mqtt",
"AssembleName": "MqttServer",
"DriverTypeEnum": "Upload",
"FilePath": "Plugins/ThingsGateway.Mqtt/ThingsGateway.Mqtt.dll",
"CreateTime": "2023/8/6 18:22:48",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087330930949,
"FileName": "ThingsGateway.Kafka",
"AssembleName": "KafkaProducer",
"DriverTypeEnum": "Upload",
"FilePath": "Plugins/ThingsGateway.Kafka/ThingsGateway.Kafka.dll",
"CreateTime": "2023/8/6 18:23:02",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 442505,
"FileName": "ThingsGateway.DLT645",
"AssembleName": "DLT645_2007",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.DLT645/ThingsGateway.DLT645.dll",
"CreateTime": "2023/8/6 18:22:33",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 442506,
"FileName": "ThingsGateway.DLT645",
"AssembleName": "DLT645_2007OverTcp",
"DriverTypeEnum": "Collect",
"FilePath": "Plugins/ThingsGateway.DLT645/ThingsGateway.DLT645.dll",
"CreateTime": "2023/8/6 18:22:33",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
}
]
}

View File

@@ -1,76 +0,0 @@
{
"RECORDS": [
{
"Id": "230000000000",
"Category": "THINGSGATEWAY_ALARMCONFIG_BASE",
"ConfigKey": "CONFIG_ALARM_ENABLE",
"ConfigValue": "False",
"Remark": "转储使能",
"SortCode": "1",
"IsDelete": false,
"UpdateTime": "2023-03-03 18:50:38.341",
"UpdateUser": "admin",
"UpdateUserId": "201725263002001"
},
{
"Id": "230000000001",
"Category": "THINGSGATEWAY_ALARMCONFIG_BASE",
"ConfigKey": "CONFIG_ALARM_DBTYPE",
"ConfigValue": "SqlServer",
"Remark": "数据库类型",
"SortCode": "2",
"IsDelete": false,
"UpdateTime": "2023-03-03 18:50:38.343",
"UpdateUser": "admin",
"UpdateUserId": "201725263002001"
},
{
"Id": "230000000002",
"Category": "THINGSGATEWAY_ALARMCONFIG_BASE",
"ConfigKey": "CONFIG_ALARM_CONNSTR",
"ConfigValue": "server=.;uid=sa;pwd=111111;database=test",
"Remark": "连接字符串",
"SortCode": "2",
"IsDelete": false,
"UpdateTime": "2023-03-03 18:50:38.343",
"UpdateUser": "admin",
"UpdateUserId": "201725263002001"
},
{
"Id": "240000000000",
"Category": "THINGSGATEWAY_HISCONFIG_BASE",
"ConfigKey": "CONFIG_HIS_ENABLE",
"ConfigValue": "False",
"Remark": "转储使能",
"SortCode": "1",
"IsDelete": false,
"UpdateTime": "2023-02-26 17:38:37.741",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "240000000001",
"Category": "THINGSGATEWAY_HISCONFIG_BASE",
"ConfigKey": "CONFIG_HIS_DBTYPE",
"ConfigValue": "QuestDB",
"Remark": "数据库类型",
"SortCode": "2",
"IsDelete": false,
"UpdateTime": "2023-02-26 17:38:37.744",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "240000000002",
"Category": "THINGSGATEWAY_HISCONFIG_BASE",
"ConfigKey": "CONFIG_HIS_CONNSTR",
"ConfigValue": "host=localhost;port=8812;username=admin;password=quest;database=qdb;ServerCompatibilityMode=NoTypeLoading;",
"Remark": "连接字符串",
"SortCode": "2",
"IsDelete": false,
"UpdateTime": "2023-02-26 17:38:37.745",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
}
]
}

View File

@@ -1,13 +0,0 @@
{
"RECORDS": [
{
"Id": "257755263002001",
"Account": "admin",
"Password": "7DA385A25A98388E",
"UserEnable": "true",
"SortCode": "1",
"IsDelete": "false",
"PermissionCodeList": [ "/openApi/collectInfo/collectDeviceList", "/openApi/collectInfo/collectVariableList", "/openApi/collectInfo/realAlarmList", "/openApi/rpc/writeDeviceMethod", "/openApi/rpc/writeVariables", "/openApi/rpc/configDeviceThread", "/openApi/rpc/upDeviceThread" ]
}
]
}

View File

@@ -1,509 +0,0 @@
{
"RECORDS": [
{
"Id": "200001",
"Title": "网关配置",
"Icon": "mdi-cog",
"Category": "MENU",
"Code": "system",
"ParentId": "0",
"SortCode": "2",
"TargetType": "None",
"IsDelete": false,
"UpdateTime": "2023-03-03 17:43:27.6423987",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "200001001",
"Title": "采集设备",
"Icon": "mdi-database-cog-outline",
"Name": "gatewayCollectDevice",
"Component": "/gatewayconfig/collectdevice",
"Category": "MENU",
"Code": "system",
"ParentId": "200001",
"SortCode": "2",
"TargetType": "SELF",
"IsDelete": false,
"UpdateTime": "2023-03-03 17:58:03.8016224",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "200001002",
"Title": "插件管理",
"Icon": "mdi-database-cog-outline",
"Name": "gatewayplugin",
"Component": "/gatewayconfig/plugin",
"Category": "MENU",
"Code": "system",
"ParentId": "200001",
"SortCode": "1",
"TargetType": "SELF",
"IsDelete": false,
"UpdateTime": "2023-03-03 17:58:00.8434735",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "200001011",
"Title": "驱动调试",
"Icon": "mdi-database-cog-outline",
"Name": "gatewaydriverdebug",
"Component": "/gatewayconfig/driverdebug",
"Category": "MENU",
"Code": "system",
"ParentId": "200001",
"SortCode": "1",
"TargetType": "SELF",
"IsDelete": false,
"UpdateTime": "2023-03-03 17:58:09.8844904",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "200001003",
"Title": "变量管理",
"Icon": "mdi-database-cog-outline",
"Name": "gatewayvariable",
"Component": "/gatewayconfig/devicevariable",
"Category": "MENU",
"Code": "system",
"ParentId": "200001",
"SortCode": "3",
"TargetType": "SELF",
"IsDelete": false,
"UpdateTime": "2023-03-03 17:58:09.8844904",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "200001903",
"Title": "中间变量",
"Icon": "mdi-database-sync-outline",
"Component": "/gatewayconfig/memoryvariable",
"Category": "MENU",
"ParentId": "200001",
"SortCode": "3",
"TargetType": "SELF",
"CreateTime": "2023-02-26 01:02:12.089",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false,
"UpdateTime": "2023-03-03 18:01:49.2309339",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "200001904",
"Title": "管理网关",
"Icon": "mdi-database-sync-outline",
"Component": "/gatewayconfig/manage",
"Category": "MENU",
"ParentId": "200001",
"SortCode": "3",
"TargetType": "SELF",
"CreateTime": "2023-02-26 01:02:12.089",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false,
"UpdateTime": "2023-03-03 18:01:49.2309339",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "200001004",
"Title": "运行状态",
"Icon": "mdi-transit-connection-horizontal",
"Component": "/gatewayruntime/devicestatus",
"Category": "MENU",
"Code": "system",
"ParentId": "389850957095173",
"SortCode": "1",
"TargetType": "SELF",
"IsDelete": false,
"UpdateTime": "2023-03-03 21:23:45.8478018",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "200001005",
"Title": "实时数据",
"Icon": "mdi-database-refresh-outline",
"Component": "/gatewayruntime/devicevariable",
"Category": "MENU",
"Code": "system",
"ParentId": "389850957095173",
"SortCode": "2",
"TargetType": "SELF",
"IsDelete": false,
"UpdateTime": "2023-03-03 17:59:12.0176321",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "389850957095173",
"Title": "网关状态",
"Icon": "mdi-transit-connection-variant",
"Category": "MENU",
"ParentId": "0",
"SortCode": "3",
"TargetType": "None",
"CreateTime": "2023-02-26 00:47:38.342",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false,
"UpdateTime": "2023-03-03 17:55:58.2367985",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "389854423286021",
"Title": "硬件信息",
"Icon": "mdi-memory",
"Component": "/gatewayruntime/hardwareinfo",
"Category": "MENU",
"ParentId": "389850957095173",
"SortCode": "4",
"TargetType": "SELF",
"CreateTime": "2023-02-26 01:01:44.580",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false,
"UpdateTime": "2023-03-03 21:24:02.3314076",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "389854535966981",
"Title": "历史数据",
"Icon": "mdi-database-sync-outline",
"Component": "/gatewayruntime/historyvalue",
"Category": "MENU",
"ParentId": "389850957095173",
"SortCode": "5",
"TargetType": "SELF",
"CreateTime": "2023-02-26 01:02:12.089",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false,
"UpdateTime": "2023-03-03 18:01:49.2309339",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "389854579716357",
"Title": "实时报警",
"Icon": "mdi-alarm-light-outline",
"Component": "/gatewayruntime/realalarm",
"Category": "MENU",
"ParentId": "389850957095173",
"SortCode": "3",
"TargetType": "SELF",
"CreateTime": "2023-02-26 01:02:22.771",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false,
"UpdateTime": "2023-03-03 17:59:32.6305265",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "389854617587973",
"Title": "历史报警",
"Icon": "mdi-database-sync-outline",
"Component": "/gatewayruntime/hisalarm",
"Category": "MENU",
"ParentId": "389850957095173",
"SortCode": "6",
"TargetType": "SELF",
"CreateTime": "2023-02-26 01:02:32.016",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false,
"UpdateTime": "2023-03-03 18:01:52.9110511",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "390053150736645",
"Title": "其他配置",
"Icon": "mdi-database-cog-outline",
"Component": "/gatewayconfig/config",
"Category": "MENU",
"ParentId": "200001",
"SortCode": "4",
"TargetType": "SELF",
"CreateTime": "2023-02-26 14:30:22.022",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false,
"UpdateTime": "2023-03-03 18:50:26.0961596",
"UpdateUser": "admin",
"UpdateUserId": "201725263002001"
},
{
"Id": "390107241025797",
"Title": "网关日志",
"Icon": "mdi-database-search-outline",
"Category": "MENU",
"ParentId": "0",
"SortCode": "3",
"TargetType": "None",
"CreateTime": "2023-02-26 18:10:27.657",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false,
"UpdateTime": "2023-03-03 17:56:30.9492488",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "390107473895685",
"Title": "Rpc日志",
"Icon": "mdi-database-search-outline",
"Component": "/gatewaylog/rpclog",
"Category": "MENU",
"ParentId": "390107241025797",
"SortCode": "1",
"TargetType": "SELF",
"CreateTime": "2023-02-26 18:11:24.513",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false,
"UpdateTime": "2023-03-03 18:02:10.321289",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "390107521245445",
"Title": "后台日志",
"Icon": "mdi-database-search-outline",
"Component": "/gatewaylog/backendlog",
"Category": "MENU",
"ParentId": "390107241025797",
"SortCode": "1",
"TargetType": "SELF",
"CreateTime": "2023-02-26 18:11:36.074",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false,
"UpdateTime": "2023-03-03 18:02:13.1842573",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "200001101",
"Title": "上传设备",
"Icon": "mdi-database-cog-outline",
"Name": "gatewayUploadDevice",
"Component": "/gatewayconfig/uploaddevice",
"Category": "MENU",
"Code": "system",
"ParentId": "200001",
"SortCode": "2",
"TargetType": "SELF",
"IsDelete": false,
"UpdateTime": "2023-03-03 17:58:06.9967903",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "391876432142597",
"Title": "清空",
"Category": "BUTTON",
"Code": "gatewayrpclogclear",
"ParentId": "390107473895685",
"SortCode": "1",
"TargetType": "None",
"CreateTime": "2023-03-03 18:09:19.0789355",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391876531253509",
"Title": "清空",
"Category": "BUTTON",
"Code": "gatewaybackendlogclear",
"ParentId": "390107521245445",
"SortCode": "2",
"TargetType": "None",
"CreateTime": "2023-03-03 18:09:43.2483009",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391876932448517",
"Title": "采集设备暂停",
"Category": "BUTTON",
"Code": "gatewaydevicepause",
"ParentId": "200001004",
"TargetType": "None",
"CreateTime": "2023-03-03 18:11:21.2313178",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391876995248389",
"Title": "设备重启",
"Category": "BUTTON",
"Code": "gatewaydevicerestart",
"ParentId": "200001004",
"TargetType": "None",
"CreateTime": "2023-03-03 18:11:36.5638931",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391877277679877",
"Title": "添加",
"Category": "BUTTON",
"Code": "gatewaypluginadd",
"ParentId": "200001002",
"TargetType": "None",
"CreateTime": "2023-03-03 18:12:45.5166707",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391877356581125",
"Title": "添加",
"Category": "BUTTON",
"Code": "gatewaycollectdeviceadd",
"ParentId": "200001001",
"TargetType": "None",
"CreateTime": "2023-03-03 18:13:04.7786123",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391877402190085",
"Title": "修改",
"Category": "BUTTON",
"Code": "gatewaycollectdeviceedit",
"ParentId": "200001001",
"TargetType": "None",
"CreateTime": "2023-03-03 18:13:15.9140326",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391877432721669",
"Title": "删除",
"Category": "BUTTON",
"Code": "gatewaycollectdevicedelete",
"ParentId": "200001001",
"TargetType": "None",
"CreateTime": "2023-03-03 18:13:23.3685636",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391877521359109",
"Title": "添加",
"Category": "BUTTON",
"Code": "gatewayvariableadd",
"ParentId": "200001003",
"TargetType": "None",
"CreateTime": "2023-03-03 18:13:45.0088195",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391877554565381",
"Title": "修改",
"Category": "BUTTON",
"Code": "gatewayvariableedit",
"ParentId": "200001003",
"TargetType": "None",
"CreateTime": "2023-03-03 18:13:53.1151901",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391877580439813",
"Title": "删除",
"Category": "BUTTON",
"Code": "gatewayvariabledelete",
"ParentId": "200001003",
"TargetType": "None",
"CreateTime": "2023-03-03 18:13:59.4322888",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391877764051205",
"Title": "添加",
"Category": "BUTTON",
"Code": "gatewayuploaddeviceadd",
"ParentId": "200001101",
"TargetType": "None",
"CreateTime": "2023-03-03 18:14:44.259772",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391877800583429",
"Title": "修改",
"Category": "BUTTON",
"Code": "gatewayuploaddeviceedit",
"ParentId": "200001101",
"TargetType": "None",
"CreateTime": "2023-03-03 18:14:53.178319",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391877824180485",
"Title": "删除",
"Category": "BUTTON",
"Code": "gatewayuploaddevicedelete",
"ParentId": "200001101",
"TargetType": "None",
"CreateTime": "2023-03-03 18:14:58.9389864",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391878193139973",
"Title": "报警配置保存",
"Category": "BUTTON",
"Code": "gatewayalarmconfig",
"ParentId": "390053150736645",
"TargetType": "None",
"CreateTime": "2023-03-03 18:16:29.0170337",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
},
{
"Id": "391878242271493",
"Title": "历史配置保存",
"Category": "BUTTON",
"Code": "gatewayhisconfig",
"ParentId": "390053150736645",
"TargetType": "None",
"CreateTime": "2023-03-03 18:16:41.0119884",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false
}
]
}

View File

@@ -1,27 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using ThingsGateway.Admin.Application;
namespace ThingsGateway.Application;
/// <summary>
/// 资源表种子数据
/// </summary>
public class SysResourceSeedData : ISqlSugarEntitySeedData<SysResource>
{
/// <inheritdoc/>
public IEnumerable<SysResource> SeedData()
{
return SeedDataUtil.GetSeedData<SysResource>("gateway_resource.json");
}
}

View File

@@ -1,531 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.DependencyInjection;
using Furion.FriendlyException;
using Mapster;
using Microsoft.AspNetCore.Components.Forms;
using MiniExcelLibs;
using System.Collections.Concurrent;
using System.Dynamic;
using System.Reflection;
using ThingsGateway.Admin.Application;
using ThingsGateway.Application.Extensions;
using ThingsGateway.Foundation.Extension.Generic;
using Yitter.IdGenerator;
namespace ThingsGateway.Application;
/// <inheritdoc cref="ICollectDeviceService"/>
[Injection(Proxy = typeof(OperDispatchProxy))]
public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceService
{
private readonly IDriverPluginService _driverPluginService;
private readonly IFileService _fileService;
/// <inheritdoc cref="ICollectDeviceService"/>
public CollectDeviceService(
IDriverPluginService driverPluginService,
IFileService fileService
)
{
_fileService = fileService;
_driverPluginService = driverPluginService;
}
/// <inheritdoc/>
[OperDesc("添加采集设备")]
public async Task AddAsync(CollectDevice input)
{
var account_Id = GetIdByName(input.Name);
if (account_Id > 0)
throw Oops.Bah($"存在重复的名称:{input.Name}");
await InsertAsync(input);//添加数据
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除
}
/// <inheritdoc/>
[OperDesc("复制采集设备")]
public async Task CopyDevAsync(IEnumerable<CollectDevice> input)
{
var newDevs = input.Adapt<List<CollectDevice>>();
newDevs.ForEach(a =>
{
a.Id = YitIdHelper.NextId();
a.Name = $"Copy-{a.Name}-{a.Id}";
});
var result = await InsertRangeAsync(newDevs);//添加数据
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除
}
/// <inheritdoc/>
[OperDesc("复制采集设备与变量")]
public async Task CopyDevAndVarAsync(IEnumerable<CollectDevice> input)
{
var variableService = App.GetService<IVariableService>();
List<DeviceVariable> variables = new();
var newDevs = input.Adapt<List<CollectDevice>>();
foreach (var item in newDevs)
{
var newId = YitIdHelper.NextId();
var deviceVariables = await Context.Queryable<DeviceVariable>().Where(a => a.DeviceId == item.Id).ToListAsync();
deviceVariables.ForEach(b =>
{
b.Id = YitIdHelper.NextId();
b.DeviceId = newId;
b.Name = $"Copy-{b.Name}-{b.Id}";
});
variables.AddRange(deviceVariables);
item.Id = newId;
item.Name = $"Copy-{item.Name}-{newId}";
}
var result = await itenant.UseTranAsync(async () =>
{
await InsertRangeAsync(newDevs);//添加数据
await Context.Insertable(variables).ExecuteCommandAsync();//添加数据
});
if (result.IsSuccess)
{
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除
}
else
{
throw Oops.Oh(result.ErrorMessage);
}
}
/// <inheritdoc/>
public long? GetIdByName(string name)
{
var data = GetCacheList(false);
return data.FirstOrDefault(it => it.Name == name)?.Id;
}
/// <inheritdoc/>
public string GetNameById(long id)
{
var data = GetCacheList(false);
return data.FirstOrDefault(it => it.Id == id)?.Name;
}
/// <inheritdoc/>
public List<DeviceTree> GetTree()
{
var data = GetCacheList(false);
var trees = data.GetTree();
return trees;
}
/// <inheritdoc/>
[OperDesc("删除采集设备")]
public async Task DeleteAsync(params long[] input)
{
//获取所有ID
if (input.Length > 0)
{
var result = await DeleteByIdsAsync(input.Cast<object>().ToArray());
var variableService = App.GetService<IVariableService>();
await Context.Deleteable<DeviceVariable>(it => input.Contains(it.DeviceId)).ExecuteCommandAsync();
variableService.DeleteVariableFromCache();
if (result)
{
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除
}
}
}
/// <inheritdoc/>
[OperDesc("编辑采集设备")]
public async Task EditAsync(CollectDeviceEditInput input)
{
var account_Id = GetIdByName(input.Name);
if (account_Id > 0 && account_Id != input.Id)
throw Oops.Bah($"存在重复的名称:{input.Name}");
if (await Context.Updateable(input.Adapt<CollectDevice>()).ExecuteCommandAsync() > 0)//修改数据
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除
}
/// <inheritdoc/>
public async Task<SqlSugarPagedList<CollectDevice>> PageAsync(CollectDevicePageInput input)
{
var query = GetPage(input);
var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页
return pageInfo;
}
/// <inheritdoc/>
private ISugarQueryable<CollectDevice> GetPage(CollectDevicePageInput input)
{
long? pluginid = 0;
if (!string.IsNullOrEmpty(input.PluginName))
{
pluginid = _driverPluginService.GetCacheList(false).FirstOrDefault(it => it.AssembleName.Contains(input.PluginName))?.Id;
}
ISugarQueryable<CollectDevice> query = Context.Queryable<CollectDevice>()
.WhereIF(!string.IsNullOrEmpty(input.Name), u => u.Name.Contains(input.Name))
.WhereIF(!string.IsNullOrEmpty(input.DeviceGroup), u => u.DeviceGroup == input.DeviceGroup)
.WhereIF(!string.IsNullOrEmpty(input.PluginName), u => u.PluginId == (pluginid ?? 0));
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
query = query.OrderBy(it => it.Id, OrderByType.Desc);//排序
return query;
}
/// <inheritdoc/>
public CollectDevice GetDeviceById(long Id)
{
var data = GetCacheList();
return data.FirstOrDefault(it => it.Id == Id);
}
/// <inheritdoc/>
public List<CollectDevice> GetCacheList(bool isMapster = true)
{
//先从Cache拿
var collectDevice = CacheStatic.Cache.Get<List<CollectDevice>>(ThingsGatewayCacheConst.Cache_CollectDevice, isMapster);
if (collectDevice == null)
{
collectDevice = Context.Queryable<CollectDevice>().ToList();
if (collectDevice != null)
{
//插入Cache
CacheStatic.Cache.Set(ThingsGatewayCacheConst.Cache_CollectDevice, collectDevice, isMapster);
}
}
return collectDevice;
}
/// <inheritdoc/>
public async Task<List<CollectDeviceRunTime>> GetCollectDeviceRuntimeAsync(long devId = 0)
{
if (devId == 0)
{
var devices = GetCacheList(false).Where(a => a.Enable).ToList();
var runtime = devices.Adapt<List<CollectDeviceRunTime>>().ToDictionary(a => a.Id);
var variableService = App.GetService<IVariableService>();
var collectVariableRunTimes = await variableService.GetDeviceVariableRuntimeAsync();
ConcurrentDictionary<long, DriverPlugin> driverPlugins = new(_driverPluginService.GetCacheList(false).ToDictionary(a => a.Id));
runtime.Values.ParallelForEach(device =>
{
driverPlugins.TryGetValue(device.PluginId, out var driverPlugin);
device.PluginName = driverPlugin?.AssembleName;
device.DeviceVariableRunTimes = collectVariableRunTimes.Where(a => a.DeviceId == device.Id).ToList();
});
collectVariableRunTimes.ParallelForEach(variable =>
{
if (runtime.TryGetValue(variable.DeviceId, out var device))
{
variable.CollectDeviceRunTime = device;
variable.DeviceName = device.Name;
}
});
return runtime.Values.ToList();
}
else
{
var device = GetCacheList(false).Where(a => a.Enable).ToList().FirstOrDefault(it => it.Id == devId);
var runtime = device.Adapt<CollectDeviceRunTime>();
var variableService = App.GetService<IVariableService>();
if (runtime == null) return new() { runtime };
var pluginName = _driverPluginService.GetNameById(device.PluginId);
var collectVariableRunTimes = await variableService.GetDeviceVariableRuntimeAsync(devId);
runtime.PluginName = pluginName;
runtime.DeviceVariableRunTimes = collectVariableRunTimes;
collectVariableRunTimes.ParallelForEach(variable =>
{
variable.CollectDeviceRunTime = runtime;
variable.DeviceName = runtime.Name;
});
return new() { runtime };
}
}
#region
/// <inheritdoc/>
public async Task<MemoryStream> ExportFileAsync(CollectDeviceInput input)
{
var query = GetPage(input.Adapt<CollectDevicePageInput>());
var data = await query.ToListAsync();
return await ExportFileAsync(data);
}
/// <inheritdoc/>
[OperDesc("导出采集设备表", IsRecordPar = false)]
public async Task<MemoryStream> ExportFileAsync(List<CollectDevice> devDatas = null)
{
devDatas ??= GetCacheList(false);
//总数据
Dictionary<string, object> sheets = new();
//设备页
List<Dictionary<string, object>> devExports = new();
//设备附加属性转成Dict<表名,List<Dict<列名,列数据>>>的形式
Dictionary<string, List<Dictionary<string, object>>> devicePropertys = new();
var driverPluginDicts = _driverPluginService.GetCacheList(false).ToDictionary(a => a.Id);
var deviceDicts = devDatas.ToDictionary(a => a.Id);
foreach (var devData in devDatas)
{
#region sheet
//设备页
var data = devData.GetType().GetPropertiesWithCache().Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>() == null);
Dictionary<string, object> devExport = new();
foreach (var item in data)
{
//描述
var desc = item.FindDisplayAttribute();
//数据源增加
devExport.Add(desc ?? item.Name, item.GetValue(devData)?.ToString());
}
driverPluginDicts.TryGetValue(devData.PluginId, out var driverPlugin);
deviceDicts.TryGetValue(devData.RedundantDeviceId, out var redundantDevice);
//设备实体没有包含插件名称,手动插入
devExport.Add(ExportHelpers.PluginName, driverPlugin.AssembleName);
//设备实体没有包含冗余设备名称,手动插入
devExport.Add(ExportHelpers.RedundantDeviceName, redundantDevice?.Name);
//添加完整设备信息
devExports.Add(devExport);
#endregion
#region sheet
//插件属性
//单个设备的行数据
Dictionary<string, object> driverInfo = new();
//没有包含设备名称,手动插入
if (devData.DevicePropertys.Count > 0)
{
driverInfo.Add(ExportHelpers.DeviceName, devData.Name);
}
foreach (var item in devData.DevicePropertys ?? new())
{
//添加对应属性数据
driverInfo.Add(item.Description, item.Value);
}
//插件名称去除首部ThingsGateway.作为表名
var pluginName = driverPlugin.AssembleName.Replace(ExportHelpers.PluginLeftName, "");
if (devicePropertys.ContainsKey(pluginName))
{
if (driverInfo.Count > 0)
devicePropertys[pluginName].Add(driverInfo);
}
else
{
if (driverInfo.Count > 0)
devicePropertys.Add(pluginName, new() { driverInfo });
}
#endregion
}
//添加设备页
sheets.Add(ExportHelpers.CollectDeviceSheetName, devExports);
//添加插件属性页
foreach (var item in devicePropertys)
{
sheets.Add(item.Key, item.Value);
}
var memoryStream = new MemoryStream();
await memoryStream.SaveAsAsync(sheets);
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
/// <inheritdoc/>
public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file)
{
_fileService.ImportVerification(file);
using var stream = new MemoryStream();
using var fs = file.OpenReadStream(512000000);
await fs.CopyToAsync(stream);
return await PreviewAsync(stream);
}
/// <inheritdoc/>
public Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(MemoryStream stream)
{
var sheetNames = MiniExcel.GetSheetNames(stream);
var deviceDicts = GetCacheList(false).ToDictionary(a => a.Name);
var pluginDicts = _driverPluginService.GetCacheList(false).ToDictionary(a => a.AssembleName);
//导入检验结果
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
//设备页
ImportPreviewOutput<CollectDevice> deviceImportPreview = new();
foreach (var sheetName in sheetNames)
{
//单页数据
var rows = stream.Query(useHeaderRow: true, sheetName: sheetName).Cast<IDictionary<string, object>>();
#region sheet
if (sheetName == ExportHelpers.CollectDeviceSheetName)
{
int row = 1;
ImportPreviewOutput<CollectDevice> importPreviewOutput = new();
ImportPreviews.Add(sheetName, importPreviewOutput);
deviceImportPreview = importPreviewOutput;
List<CollectDevice> devices = new();
rows.ForEach(item =>
{
try
{
var device = ((ExpandoObject)item).ConvertToEntity<CollectDevice>(true);
#region
//转化插件名称
var hasPlugin = item.TryGetValue(ExportHelpers.PluginName, out var pluginObj);
if (pluginObj == null || !pluginDicts.TryGetValue(pluginObj.ToString(), out var plugin))
{
//找不到对应的插件
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((row++, false, $"{ExportHelpers.PluginName}不存在"));
return;
}
//转化冗余设备名称
var hasRedundant = item.TryGetValue(ExportHelpers.PluginName, out var redundantObj);
#endregion
//插件ID、设备ID、冗余设备ID都需要手动补录
device.PluginId = plugin.Id;
if (hasRedundant && deviceDicts.TryGetValue(redundantObj.ToString(), out var rendundantDevice))
{
device.RedundantDeviceId = rendundantDevice.Id;
}
device.Id = deviceDicts.TryGetValue(device.Name, out var collectDevice) ? collectDevice.Id : YitIdHelper.NextId();
devices.Add(device);
importPreviewOutput.Results.Add((row++, true, "成功"));
return;
}
catch (Exception ex)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((row++, false, ex.Message));
return;
}
});
importPreviewOutput.Data = devices.ToDictionary(a => a.Name);
}
#endregion
else
{
int row = 1;
ImportPreviewOutput<string> importPreviewOutput = new();
ImportPreviews.Add(sheetName, importPreviewOutput);
//插件属性需加上前置名称
//var newName = ExportHelpers.PluginLeftName + sheetName;
var newName = sheetName;
var pluginId = _driverPluginService.GetIdByName(newName);
if (pluginId == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((row++, false, $"插件{newName}不存在"));
continue;
}
var driverPlugin = _driverPluginService.GetDriverPluginById(pluginId.Value);
var pluginSingletonService = App.GetService<PluginSingletonService>();
var driver = (DriverBase)pluginSingletonService.GetDriver(driverPlugin);
var propertys = driver.DriverPropertys.GetType().GetPropertiesWithCache()
.Where(a => a.GetCustomAttribute<DevicePropertyAttribute>() != null)
.ToDictionary(a => a.FindDisplayAttribute(a => a.GetCustomAttribute<DevicePropertyAttribute>()?.Description));
rows.ForEach(item =>
{
try
{
List<DependencyProperty> devices = new();
foreach (var keyValuePair in item)
{
if (propertys.TryGetValue(keyValuePair.Key, out var propertyInfo))
{
devices.Add(new()
{
PropertyName = propertyInfo.Name,
Description = keyValuePair.Key.ToString(),
Value = keyValuePair.Value?.ToString()
});
}
}
//转化插件名称
var value = item[ExportHelpers.DeviceName];
deviceImportPreview.Data[value.ToString()].DevicePropertys = devices;
importPreviewOutput.Results.Add((row++, true, "成功"));
return;
}
catch (Exception ex)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((row++, false, ex.Message));
return;
}
});
}
}
return Task.FromResult(ImportPreviews);
}
/// <inheritdoc/>
[OperDesc("导入采集设备表", IsRecordPar = false)]
public async Task ImportAsync(Dictionary<string, ImportPreviewOutputBase> input)
{
var collectDevices = new List<CollectDevice>();
foreach (var item in input)
{
if (item.Key == ExportHelpers.CollectDeviceSheetName)
{
var collectDeviceImports = ((ImportPreviewOutput<CollectDevice>)item.Value).Data;
collectDevices = collectDeviceImports.Values.Adapt<List<CollectDevice>>();
break;
}
}
await Context.Storageable(collectDevices).ExecuteCommandAsync();
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除
}
#endregion
}

View File

@@ -1,88 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Microsoft.AspNetCore.Components.Forms;
namespace ThingsGateway.Application;
/// <summary>
/// 采集设备服务
/// </summary>
public interface ICollectDeviceService : ITransient
{
/// <summary>
/// 添加设备
/// </summary>
Task AddAsync(CollectDevice input);
/// <summary>
/// 复制设备
/// </summary>
Task CopyDevAsync(IEnumerable<CollectDevice> input);
/// <summary>
/// 复制设备与变量
/// </summary>
Task CopyDevAndVarAsync(IEnumerable<CollectDevice> input);
/// <summary>
/// 删除设备
/// </summary>
Task DeleteAsync(params long[] input);
/// <summary>
/// 编辑设备
/// </summary>
Task EditAsync(CollectDeviceEditInput input);
/// <summary>
/// 导出Excel
/// </summary>
Task<MemoryStream> ExportFileAsync(List<CollectDevice> devDatas = null);
/// <summary>
/// 导出Excel
/// </summary>
Task<MemoryStream> ExportFileAsync(CollectDeviceInput input);
/// <summary>
/// 获取缓存
/// </summary>
List<CollectDevice> GetCacheList(bool isMapster = true);
/// <summary>
/// 获取设备运行状态
/// </summary>
Task<List<CollectDeviceRunTime>> GetCollectDeviceRuntimeAsync(long devId = 0);
/// <summary>
/// 根据ID获取设备
/// </summary>
CollectDevice GetDeviceById(long Id);
/// <summary>
/// 根据名称获取ID
/// </summary>
long? GetIdByName(string name);
/// <summary>
/// 根据ID获取名称
/// </summary>
string GetNameById(long id);
/// <summary>
/// 获取设备组或名称的树节点
/// </summary>
List<DeviceTree> GetTree();
/// <summary>
/// 导入
/// </summary>
Task ImportAsync(Dictionary<string, ImportPreviewOutputBase> input);
/// <summary>
/// 分页查询
/// </summary>
Task<SqlSugarPagedList<CollectDevice>> PageAsync(CollectDevicePageInput input);
/// <summary>
/// 导入验证
/// </summary>
Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file);
}

View File

@@ -1,151 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.DependencyInjection;
using Furion.FriendlyException;
using System.Data;
using ThingsGateway.Admin.Application;
using Yitter.IdGenerator;
namespace ThingsGateway.Application;
/// <inheritdoc cref="IDriverPluginService"/>
[Injection(Proxy = typeof(OperDispatchProxy))]
public partial class DriverPluginService : DbRepository<DriverPlugin>, IDriverPluginService
{
/// <inheritdoc/>
[OperDesc("添加/更新插件")]
public async Task AddAsync(DriverPluginAddInput input)
{
var pluginService = App.GetService<PluginSingletonService>();
var datas = await pluginService.TestAddDriverAsync(input);
var driverPlugins = GetCacheList();
foreach (var item in datas)
{
var data = driverPlugins.FirstOrDefault(a => a.AssembleName == item.AssembleName);
if (data != null)
{
item.Id = data.Id;
}
else
{
item.Id = YitIdHelper.NextId();
}
}
var delete = driverPlugins.Where(a => a.FilePath == datas.FirstOrDefault()?.FilePath).ToList();
//事务
var result = await itenant.UseTranAsync(async () =>
{
await Context.Deleteable(delete).ExecuteCommandAsync();
await Context.Storageable(datas).ExecuteCommandAsync();
});
if (result.IsSuccess)//如果成功了
{
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_DriverPlugin);//cache删除
}
else
{
throw Oops.Oh(result.ErrorMessage);
}
}
/// <inheritdoc/>
public List<DriverPlugin> GetCacheList(bool isMapster = true)
{
//先从Cache拿
var driverPlugins = CacheStatic.Cache.Get<List<DriverPlugin>>(ThingsGatewayCacheConst.Cache_DriverPlugin, isMapster);
if (driverPlugins == null)
{
driverPlugins = Context.Queryable<DriverPlugin>()
.Select((u) => new DriverPlugin { Id = u.Id.SelectAll() })
.ToList();
if (driverPlugins != null)
{
//插入Cache
CacheStatic.Cache.Set(ThingsGatewayCacheConst.Cache_DriverPlugin, driverPlugins, isMapster);
}
}
return driverPlugins;
}
/// <inheritdoc/>
public DriverPlugin GetDriverPluginById(long Id)
{
var data = GetCacheList();
return data.FirstOrDefault(it => it.Id == Id);
}
/// <inheritdoc/>
public List<DriverPluginCategory> GetDriverPluginChildrenList(DriverEnum? driverTypeEnum = null)
{
var data = GetCacheList(false);
if (driverTypeEnum != null)
{
data = data.Where(a => a.DriverTypeEnum == driverTypeEnum).ToList();
}
var driverPluginCategories = data.GroupBy(a => a.FileName).Select(it =>
{
var childrens = new List<DriverPluginCategory>();
foreach (var item in it)
{
childrens.Add(new DriverPluginCategory
{
Id = item.Id,
Name = item.AssembleName,
}
);
}
return new DriverPluginCategory
{
Id = YitIdHelper.NextId(),
Name = it.Key,
Children = childrens,
};
});
return driverPluginCategories.ToList();
}
/// <inheritdoc/>
public long? GetIdByName(string name)
{
var data = GetCacheList(false);
return data.FirstOrDefault(it => it.AssembleName == name)?.Id;
}
/// <inheritdoc/>
public string GetNameById(long id)
{
var data = GetCacheList(false);
return data.FirstOrDefault(it => it.Id == id)?.AssembleName;
}
/// <inheritdoc/>
public async Task<SqlSugarPagedList<DriverPlugin>> PageAsync(DriverPluginPageInput input)
{
var query = Context.Queryable<DriverPlugin>()
.WhereIF(!string.IsNullOrEmpty(input.Name), u => u.AssembleName.Contains(input.Name))//根据关键字查询
.WhereIF(!string.IsNullOrEmpty(input.FileName), u => u.FileName.Contains(input.FileName));//根据关键字查询
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
query = query.OrderBy(it => it.Id, OrderByType.Desc);//排序
var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页
return pageInfo;
}
}

View File

@@ -1,79 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Application;
/// <summary>
/// 上传设备添加DTO
/// </summary>
public class UploadDeviceAddInput : UploadDeviceEditInput
{
/// <inheritdoc/>
[Required(ErrorMessage = "不能为空")]
public override string Name { get; set; }
/// <inheritdoc/>
[MinValue(1, ErrorMessage = "插件不能为空")]
public override long PluginId { get; set; }
/// <inheritdoc/>
public override bool IsLogOut { get; set; } = true;
/// <inheritdoc/>
public override bool Enable { get; set; } = true;
}
/// <summary>
/// 上传设备修改DTO
/// </summary>
public class UploadDeviceEditInput : UploadDevice
{
/// <inheritdoc/>
[Required(ErrorMessage = "不能为空")]
public override string Name { get; set; }
/// <inheritdoc/>
[MinValue(1, ErrorMessage = "插件不能为空")]
public override long PluginId { get; set; }
}
/// <summary>
/// 上传设备分页查询
/// </summary>
public class UploadDevicePageInput : BasePageInput
{
/// <inheritdoc/>
[Description("设备名称")]
public string Name { get; set; }
/// <inheritdoc/>
[Description("插件名称")]
public string PluginName { get; set; }
/// <inheritdoc/>
[Description("设备组")]
public string DeviceGroup { get; set; }
}
/// <summary>
/// 上传设备分页查询
/// </summary>
public class UploadDeviceInput
{
/// <inheritdoc/>
[Description("设备名称")]
public string Name { get; set; }
/// <inheritdoc/>
[Description("插件名称")]
public string PluginName { get; set; }
/// <inheritdoc/>
[Description("设备组")]
public string DeviceGroup { get; set; }
}

View File

@@ -1,85 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Microsoft.AspNetCore.Components.Forms;
namespace ThingsGateway.Application;
/// <summary>
/// 上传设备服务
/// </summary>
public interface IUploadDeviceService : ITransient
{
/// <summary>
/// Sql连接对象
/// </summary>
public ISqlSugarClient Context { get; set; }
/// <summary>
/// 添加上传设备
/// </summary>
Task AddAsync(UploadDevice input);
/// <summary>
/// 复制设备
/// </summary>
Task CopyDevAsync(IEnumerable<UploadDevice> input);
/// <summary>
/// 删除设备
/// </summary>
Task DeleteAsync(params long[] input);
/// <summary>
/// 编辑设备
/// </summary>
Task EditAsync(UploadDeviceEditInput input);
/// <summary>
/// 导出
/// </summary>
Task<MemoryStream> ExportFileAsync(List<UploadDevice> devDatas = null);
/// <summary>
/// 导出
/// </summary>
Task<MemoryStream> ExportFileAsync(UploadDeviceInput input);
/// <summary>
/// 获取缓存
/// </summary>
List<UploadDevice> GetCacheList(bool isMapster = true);
/// <summary>
/// 根据ID获取设备
/// </summary>
UploadDevice GetDeviceById(long Id);
/// <summary>
/// 根据名称获取ID
/// </summary>
long? GetIdByName(string name);
/// <summary>
/// 根据ID获取名称
/// </summary>
string GetNameById(long id);
/// <summary>
/// 获取上传设备运行状态
/// </summary>
List<UploadDeviceRunTime> GetUploadDeviceRuntime(long devId = 0);
/// <summary>
/// 导入
/// </summary>
Task ImportAsync(Dictionary<string, ImportPreviewOutputBase> input);
/// <summary>
/// 分页
/// </summary>
Task<SqlSugarPagedList<UploadDevice>> PageAsync(UploadDevicePageInput input);
/// <summary>
/// 导入验证
/// </summary>
Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file);
}

View File

@@ -1,101 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Microsoft.AspNetCore.Components.Forms;
namespace ThingsGateway.Application;
/// <summary>
/// 变量数据服务
/// </summary>
public interface IVariableService : ITransient
{
/// <summary>
/// 添加变量
/// </summary>
Task AddAsync(DeviceVariable input);
/// <summary>
/// 添加变量
/// </summary>
Task AddBatchAsync(List<DeviceVariable> input);
/// <summary>
/// 清空设备变量
/// </summary>
Task ClearDeviceVariableAsync();
/// <summary>
/// 清空中间变量
/// </summary>
/// <returns></returns>
Task ClearMemoryVariableAsync();
/// <summary>
/// 删除变量
/// </summary>
Task DeleteAsync(params long[] input);
/// <summary>
/// 删除变量缓存
/// </summary>
void DeleteVariableFromCache();
/// <summary>
/// 编辑变量
/// </summary>
Task EditAsync(DeviceVariable input);
/// <summary>
/// 导出
/// </summary>
Task<MemoryStream> ExportFileAsync(List<DeviceVariable> collectDeviceVariables = null, string deviceName = null);
/// <summary>
/// 导出
/// </summary>
Task<MemoryStream> ExportFileAsync(MemoryVariableInput input);
/// <summary>
/// 获取变量运行状态
/// </summary>
Task<List<DeviceVariableRunTime>> GetDeviceVariableRuntimeAsync(long devId = 0);
/// <summary>
/// 获取中间变量运行态
/// </summary>
/// <returns></returns>
Task<List<DeviceVariableRunTime>> GetMemoryVariableRuntimeAsync();
/// <summary>
/// 导入
/// </summary>
Task ImportAsync(Dictionary<string, ImportPreviewOutputBase> input);
/// <summary>
/// 导出
/// </summary>
/// <param name="devDatas"></param>
/// <returns></returns>
Task<MemoryStream> MemoryVariableExportFileAsync(List<MemoryVariable> devDatas = null);
/// <summary>
/// 导入
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
Task<Dictionary<string, ImportPreviewOutputBase>> MemoryVariablePreviewAsync(IBrowserFile file);
/// <summary>
/// 分页查询
/// </summary>
Task<SqlSugarPagedList<DeviceVariable>> PageAsync(VariablePageInput input);
/// <summary>
/// 导入验证
/// </summary>
Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file);
}

View File

@@ -1,49 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
</PropertyGroup>
<ItemGroup>
<None Remove="SeedData\Json\driver_plugin.json" />
<None Remove="SeedData\Json\gateway_openapi_user.json" />
<None Remove="SeedData\Json\gateway_relation.json" />
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<Content Include="SeedData\Json\driver_plugin.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="SeedData\Json\gateway_config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="SeedData\Json\gateway_resource.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="SeedData\Json\gateway_openapi_user.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="SeedData\Json\gateway_relation.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CodingSeb.ExpressionEvaluator" Version="1.4.39" />
<PackageReference Include="Hardware.Info" Version="11.1.1.1" />
<PackageReference Include="CS-Script" Version="4.8.1" />
<!--CS-Script与Furion冲突直接安装覆盖版本-->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.7.0" />
<PackageReference Include="MQTTnet" Version="4.3.1.873" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
<ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,684 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.Logging.Extensions;
using Mapster;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using ThingsGateway.Admin.Application;
using ThingsGateway.Application.Extensions;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
using ThingsGateway.Foundation.Extension.Generic;
using TouchSocket.Core;
using UAParser;
using Yitter.IdGenerator;
namespace ThingsGateway.Application;
/// <summary>
/// 设备采集报警后台服务
/// </summary>
public class AlarmWorker : BackgroundService
{
private readonly GlobalDeviceData _globalDeviceData;
private readonly ILogger<AlarmWorker> _logger;
/// <inheritdoc cref="AlarmWorker"/>
public AlarmWorker(ILogger<AlarmWorker> logger)
{
_logger = logger;
_globalDeviceData = ServiceHelper.Services.GetService<GlobalDeviceData>();
}
/// <summary>
/// 报警变化事件
/// </summary>
public event VariableChangeEventHandler OnAlarmChanged;
/// <summary>
/// 设备状态变化事件
/// </summary>
public event DelegateOnDeviceChanged OnDeviceStatusChanged;
/// <summary>
/// 实时报警列表
/// </summary>
public ConcurrentList<DeviceVariableRunTime> RealAlarmDeviceVariables { get; set; } = new();
/// <summary>
/// 服务状态
/// </summary>
public OperResult StatuString { get; set; } = new OperResult("初始化");
private ConcurrentQueue<DeviceVariableRunTime> DeviceVariables { get; set; } = new();
private ConcurrentQueue<HistoryAlarm> HisAlarmDeviceVariables { get; set; } = new();
/// <summary>
/// 获取数据库链接
/// </summary>
/// <returns></returns>
public async Task<OperResult<SqlSugarClient>> GetAlarmDbAsync()
{
var ConfigService = App.GetService<IConfigService>();
var alarmEnable = (await ConfigService.GetByConfigKeyAsync(ThingsGatewayConfigConst.ThingGateway_AlarmConfig_Base, ThingsGatewayConfigConst.Config_Alarm_Enable))?.ConfigValue?.ToBoolean();
var alarmDbType = (await ConfigService.GetByConfigKeyAsync(ThingsGatewayConfigConst.ThingGateway_AlarmConfig_Base, ThingsGatewayConfigConst.Config_Alarm_DbType))?.ConfigValue;
var alarmConnstr = (await ConfigService.GetByConfigKeyAsync(ThingsGatewayConfigConst.ThingGateway_AlarmConfig_Base, ThingsGatewayConfigConst.Config_Alarm_ConnStr))?.ConfigValue;
if (!(alarmEnable == true))
{
return new OperResult<SqlSugarClient>("历史报警已配置为Disable");
}
var configureExternalServices = new ConfigureExternalServices
{
EntityService = (type, column) => // 修改列可空-1、带?问号 2、String类型若没有Required
{
if ((type.PropertyType.IsGenericType && type.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
|| (type.PropertyType == typeof(string) && type.GetCustomAttribute<RequiredAttribute>() == null))
column.IsNullable = true;
},
};
DbType type = DbType.SqlServer;
if (!string.IsNullOrEmpty(alarmDbType))
{
if (Enum.TryParse<DbType>(alarmDbType, ignoreCase: true, out var result))
{
type = result;
}
else
{
return new OperResult<SqlSugarClient>("数据库类型转换失败");
}
}
var sqlSugarClient = new SqlSugarClient(new ConnectionConfig()
{
ConnectionString = alarmConnstr,//连接字符串
DbType = type,//数据库类型
IsAutoCloseConnection = true, //不设成true要手动close
ConfigureExternalServices = configureExternalServices,
MoreSettings = new ConnMoreSettings
{
SqlServerCodeFirstNvarchar = true,//设置默认nvarchar
TableEnumIsString = true,
},
});
return OperResult.CreateSuccessResult(sqlSugarClient);
}
/// <summary>
/// 获取bool报警类型
/// </summary>
private static AlarmEnum GetBoolAlarmCode(DeviceVariableRunTime tag, out string limit, out string expressions, out string text)
{
limit = string.Empty;
expressions = string.Empty;
text = string.Empty;
if (tag.BoolCloseAlarmEnable && tag.Value.ToBoolean() == false)
{
limit = false.ToString();
expressions = tag.BoolCloseRestrainExpressions;
text = tag.BoolCloseAlarmText;
return AlarmEnum.Close;
}
if (tag.BoolOpenAlarmEnable && tag.Value.ToBoolean() == true)
{
limit = true.ToString();
expressions = tag.BoolOpenRestrainExpressions;
text = tag.BoolOpenAlarmText;
return AlarmEnum.Open;
}
return AlarmEnum.None;
}
/// <summary>
/// 获取value报警类型
/// </summary>
private static AlarmEnum GetDecimalAlarmDegree(DeviceVariableRunTime tag, out string limit, out string expressions, out string text)
{
limit = string.Empty;
expressions = string.Empty;
text = string.Empty;
if (tag.HHAlarmEnable && tag.Value.ToDecimal() > tag.HHAlarmCode.ToDecimal())
{
limit = tag.HHAlarmCode.ToString();
expressions = tag.HHRestrainExpressions;
text = tag.HHAlarmText;
return AlarmEnum.HH;
}
if (tag.HAlarmEnable && tag.Value.ToDecimal() > tag.HAlarmCode.ToDecimal())
{
limit = tag.HAlarmCode.ToString();
expressions = tag.HRestrainExpressions;
text = tag.HAlarmText;
return AlarmEnum.H;
}
if (tag.LAlarmEnable && tag.Value.ToDecimal() < tag.LAlarmCode.ToDecimal())
{
limit = tag.LAlarmCode.ToString();
expressions = tag.LRestrainExpressions;
text = tag.LAlarmText;
return AlarmEnum.L;
}
if (tag.LLAlarmEnable && tag.Value.ToDecimal() < tag.LLAlarmCode.ToDecimal())
{
limit = tag.LLAlarmCode.ToString();
expressions = tag.LLRestrainExpressions;
text = tag.LLAlarmText;
return AlarmEnum.LL;
}
return AlarmEnum.None;
}
#region worker服务
/// <inheritdoc/>
public override async Task StartAsync(CancellationToken token)
{
_logger?.LogInformation("报警服务启动");
await base.StartAsync(token);
}
/// <inheritdoc/>
public override Task StopAsync(CancellationToken token)
{
_logger?.LogInformation("报警服务停止");
return base.StopAsync(token);
}
/// <inheritdoc/>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Delay(5000, stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(60000, stoppingToken);
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
}
}
#endregion
#region
/// <summary>
/// 循环线程取消标识
/// </summary>
private ConcurrentList<CancellationTokenSource> StoppingTokens = new();
/// <summary>
/// 全部重启锁
/// </summary>
private readonly EasyLock restartLock = new();
private Task HisAlarmTask;
private bool IsExited;
private Task RealAlarmTask;
private CacheDb CacheDb { get; set; }
/// <summary>
/// 重启
/// </summary>
/// <returns></returns>
public async Task RestartAsync()
{
await StopAsync();
await StartAsync();
}
internal async Task StartAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
foreach (var item in _globalDeviceData.CollectDevices)
{
item.DeviceStatusChange += DeviceStatusChange;
item.DeviceVariableRunTimes?.ForEach(v => { v.VariableCollectChange += DeviceVariableChange; });
}
StoppingTokens.Add(new());
await InitAsync();
if (RealAlarmTask.Status == TaskStatus.Created)
RealAlarmTask.Start();
if (HisAlarmTask.Status == TaskStatus.Created)
HisAlarmTask.Start();
IsExited = false;
}
catch (Exception ex)
{
_logger.LogError(ex, "重启错误");
}
finally
{
restartLock.Release();
}
}
internal async Task StopAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
IsExited = true;
foreach (var device in _globalDeviceData.CollectDevices)
{
device.DeviceStatusChange -= DeviceStatusChange;
device.DeviceVariableRunTimes?.ForEach(v => { v.VariableCollectChange -= DeviceVariableChange; });
}
foreach (var token in StoppingTokens)
{
token.Cancel();
}
if (RealAlarmTask != null)
{
try
{
_logger?.LogInformation($"实时报警线程停止中");
await RealAlarmTask.WaitAsync(TimeSpan.FromSeconds(10));
_logger?.LogInformation($"实时报警线程已停止");
}
catch (ObjectDisposedException)
{
}
catch (TimeoutException)
{
_logger?.LogWarning($"实时报警线程停止超时,已强制取消");
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "等待实时报警线程停止错误");
}
RealAlarmTask?.SafeDispose();
}
if (HisAlarmTask != null)
{
try
{
_logger?.LogInformation($"历史报警线程停止中");
await HisAlarmTask.WaitAsync(TimeSpan.FromSeconds(10));
_logger?.LogInformation($"历史报警线程已停止");
}
catch (ObjectDisposedException)
{
}
catch (TimeoutException)
{
_logger?.LogWarning($"历史报警线程停止超时,已强制取消");
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "等待历史报警线程停止错误");
}
}
HisAlarmTask?.SafeDispose();
foreach (var token in StoppingTokens)
{
token.SafeDispose();
}
StoppingTokens.Clear();
}
catch (Exception ex)
{
_logger.LogError(ex, "重启错误");
}
finally
{
restartLock.Release();
}
}
private void AlarmAnalysis(DeviceVariableRunTime item)
{
string limit;
string ex;
string text;
AlarmEnum alarmEnum;
if (item.DataTypeEnum.GetSystemType() == typeof(bool))
{
alarmEnum = GetBoolAlarmCode(item, out limit, out ex, out text);
}
else
{
alarmEnum = GetDecimalAlarmDegree(item, out limit, out ex, out text);
}
if (alarmEnum == AlarmEnum.None)
{
//需恢复报警,如果存在的话
AlarmChange(item, null, text, EventEnum.Finish, alarmEnum);
}
else
{
//需更新报警,不管是否存在
if (!string.IsNullOrEmpty(ex))
{
var data = ex.GetExpressionsResult(item.Value);
if (data is bool result)
{
if (result)
{
AlarmChange(item, limit, text, EventEnum.Alarm, alarmEnum);
}
}
}
else
{
AlarmChange(item, limit, text, EventEnum.Alarm, alarmEnum);
}
}
}
private void AlarmChange(DeviceVariableRunTime item, object limit, string text, EventEnum eventEnum, AlarmEnum alarmEnum)
{
if (eventEnum == EventEnum.Finish)
{
//实时报警没有找到的话直接返回
if (!RealAlarmDeviceVariables.Any(it => it.Id == item.Id))
{
return;
}
}
else if (eventEnum == EventEnum.Alarm)
{
var variable = RealAlarmDeviceVariables.FirstOrDefault(it => it.Id == item.Id);
if (variable != null)
{
if (item.AlarmTypeEnum == alarmEnum)
return;
}
}
if (eventEnum == EventEnum.Alarm)
{
item.AlarmTypeEnum = alarmEnum;
item.EventTypeEnum = eventEnum;
item.AlarmLimit = limit.ToString();
item.AlarmCode = item.Value.ToString();
item.AlarmText = text;
item.AlarmTime = SysDateTimeExtensions.CurrentDateTime;
item.EventTime = SysDateTimeExtensions.CurrentDateTime;
}
else if (eventEnum == EventEnum.Finish)
{
var oldAlarm = RealAlarmDeviceVariables.FirstOrDefault(it => it.Id == item.Id);
item.AlarmTypeEnum = oldAlarm.AlarmTypeEnum;
item.EventTypeEnum = eventEnum;
item.AlarmLimit = oldAlarm.AlarmLimit;
item.AlarmCode = item.Value.ToString();
item.AlarmText = text;
item.EventTime = SysDateTimeExtensions.CurrentDateTime;
}
else if (eventEnum == EventEnum.Check)
{
item.AlarmTypeEnum = alarmEnum;
item.EventTypeEnum = eventEnum;
item.AlarmLimit = limit.ToString();
item.AlarmCode = item.Value.ToString();
item.AlarmText = text;
item.EventTime = SysDateTimeExtensions.CurrentDateTime;
}
OnAlarmChanged?.Invoke(item.Adapt<DeviceVariableRunTime>());
if (!IsExited)
{
HisAlarmDeviceVariables.Enqueue(item.Adapt<HistoryAlarm>());
}
if (eventEnum == EventEnum.Alarm)
{
RealAlarmDeviceVariables.RemoveWhere(it => it.Id == item.Id);
RealAlarmDeviceVariables.Add(item);
}
else
{
RealAlarmDeviceVariables.RemoveWhere(it => it.Id == item.Id);
}
}
private void DeviceStatusChange(CollectDeviceRunTime device)
{
OnDeviceStatusChanged?.Invoke(device.Adapt<CollectDeviceRunTime>());
}
private void DeviceVariableChange(DeviceVariableRunTime variable)
{
//这里不能序列化变量,报警服务需改变同一个变量指向的属性
DeviceVariables.Enqueue(variable);
}
/// <summary>
/// 初始化
/// </summary>
private async Task InitAsync()
{
CacheDb = new("HistoryAlarmCache");
var stoppingToken = StoppingTokens.Last().Token;
RealAlarmTask = await Task.Factory.StartNew(async () =>
{
_logger?.LogInformation($"实时报警线程开始");
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(500, stoppingToken);
var list = DeviceVariables.ToListWithDequeue();
foreach (var item in list)
{
if (stoppingToken.IsCancellationRequested)
break;
if (!item.AlarmEnable) continue;
AlarmAnalysis(item);
}
if (stoppingToken.IsCancellationRequested)
break;
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
_logger?.LogWarning(ex, $"实时报警循环异常");
}
}
}
, TaskCreationOptions.LongRunning);
HisAlarmTask = await Task.Factory.StartNew(async () =>
{
_logger?.LogInformation($"历史报警线程开始");
await Task.Yield();//返回线程控制,不再阻塞
try
{
await Task.Delay(500, stoppingToken);
var result = await GetAlarmDbAsync();
if (!result.IsSuccess)
{
_logger?.LogWarning($"历史报警线程即将退出:" + result.Message);
StatuString = new OperResult($"已退出:{result.Message}");
IsExited = true;
return;
}
else
{
var sqlSugarClient = result.Content;
bool isSuccess = true;
/***创建/更新单个表***/
try
{
await sqlSugarClient.Queryable<HistoryAlarm>().FirstAsync(stoppingToken);
isSuccess = true;
StatuString = OperResult.CreateSuccessResult();
}
catch (Exception)
{
if (stoppingToken.IsCancellationRequested)
{
IsExited = true;
return;
}
try
{
_logger.LogWarning("连接历史报警表失败,尝试初始化表");
sqlSugarClient.CodeFirst.InitTables(typeof(HistoryAlarm));
isSuccess = true;
StatuString = OperResult.CreateSuccessResult();
}
catch (Exception ex)
{
isSuccess = false;
StatuString = new OperResult(ex);
_logger.LogWarning(ex, "连接历史报警数据库失败");
}
}
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(500, stoppingToken);
if (stoppingToken.IsCancellationRequested)
break;
//缓存值
var cacheData = await CacheDb.GetCacheData();
if (cacheData.Count > 0)
{
var data = cacheData.SelectMany(a => a.CacheStr.FromJsonString<List<HistoryAlarm>>()).ToList();
try
{
var count = await sqlSugarClient.Insertable(data).ExecuteCommandAsync(stoppingToken);
await CacheDb.DeleteCacheData(cacheData.Select(a => a.Id).ToArray());
}
catch (Exception ex)
{
if (isSuccess)
_logger.LogWarning(ex, "写入历史报警失败");
}
}
if (stoppingToken.IsCancellationRequested)
break;
var list = HisAlarmDeviceVariables.ToListWithDequeue();
if (list.Count != 0)
{
////Sql保存
list.ForEach(it =>
{
it.Id = YitIdHelper.NextId();
});
//插入
try
{
await sqlSugarClient.Insertable(list).ExecuteCommandAsync(stoppingToken);
isSuccess = true;
}
catch (Exception ex)
{
if (isSuccess)
_logger.LogWarning(ex, "写入历史报警失败");
var cacheDatas = list.ChunkTrivialBetter(500);
foreach (var a in cacheDatas)
{
await CacheDb.AddCacheData("", a.ToJsonString(), 50000);
}
}
}
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
if (isSuccess)
_logger?.LogWarning($"历史报警循环异常:" + ex.Message);
StatuString = new OperResult(ex);
isSuccess = false;
}
}
IsExited = true;
}
}
catch (TaskCanceledException)
{
IsExited = true;
}
catch (ObjectDisposedException)
{
IsExited = true;
}
catch (Exception ex)
{
IsExited = true;
_logger?.LogError($"历史报警异常:" + ex.Message);
}
}
, TaskCreationOptions.LongRunning);
}
#endregion
}

View File

@@ -1,781 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.FriendlyException;
using Furion.Logging.Extensions;
using Mapster;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using System.Reflection;
using ThingsGateway.Application.Extensions;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.Extension.Byte;
using ThingsGateway.Foundation.Extension.Generic;
using TouchSocket.Core;
namespace ThingsGateway.Application;
/// <summary>
/// 设备子线程服务
/// </summary>
public class CollectDeviceCore
{
/// <summary>
/// 特殊方法变量
/// </summary>
public List<DeviceVariableMethodSource> DeviceVariableMethodReads = new();
/// <summary>
/// 特殊方法变量,不参与轮询执行
/// </summary>
public List<DeviceVariableMethodSource> DeviceVariableMethodSources = new();
/// <summary>
/// 变量打包
/// </summary>
public List<DeviceVariableSourceRead> DeviceVariableSourceReads = new();
/// <summary>
/// 全局插件服务
/// </summary>
private readonly PluginSingletonService _pluginService;
/// <summary>
/// 读写锁
/// </summary>
private readonly EasyLock easyLock = new();
/// <summary>
/// 当前设备信息
/// </summary>
private CollectDeviceRunTime _device;
/// <summary>
/// 当前的驱动插件实例
/// </summary>
private CollectBase _driver;
/// <summary>
/// 日志
/// </summary>
private ILogger _logger;
/// <summary>
/// 是否初始化成功
/// </summary>
private bool isInitSuccess = true;
/// <inheritdoc cref="CollectDeviceCore"/>
public CollectDeviceCore()
{
_pluginService = ServiceHelper.Services.GetService<PluginSingletonService>();
GlobalDeviceData = ServiceHelper.Services.GetService<GlobalDeviceData>();
DriverPluginService = App.GetService<IDriverPluginService>();
}
/// <summary>
/// 当前设备
/// </summary>
public CollectDeviceRunTime Device => _device;
/// <summary>
/// 当前设备Id
/// </summary>
public long DeviceId => (long)(_device?.Id.ToLong());
/// <summary>
/// 当前插件
/// </summary>
public CollectBase Driver => _driver;
/// <summary>
/// 初始化成功
/// </summary>
public bool IsInitSuccess => isInitSuccess;
/// <summary>
/// 日志
/// </summary>
public ILogger Logger => _logger;
/// <summary>
/// 当前设备全部特殊方法,执行初始化后获取正确值
/// </summary>
public List<MethodInfo> Methods { get; private set; }
/// <summary>
/// 当前设备全部设备属性,执行初始化后获取正确值
/// </summary>
public List<DependencyProperty> Propertys { get; private set; }
private IDriverPluginService DriverPluginService { get; set; }
private GlobalDeviceData GlobalDeviceData { get; set; }
/// <summary>
/// 暂停采集
/// </summary>
public void PasueThread(bool keepRun)
{
lock (this)
{
var str = keepRun == false ? "设备线程采集暂停" : "设备线程采集继续";
_logger?.LogInformation($"{str}:{_device.Name}");
this.Device.KeepRun = keepRun;
}
}
#region
/// <summary>
/// 获取插件
/// </summary>
/// <returns></returns>
private CollectBase CreatDriver()
{
var driverPlugin = DriverPluginService.GetDriverPluginById(_device.PluginId);
if (driverPlugin != null)
{
try
{
_driver = (CollectBase)_pluginService.GetDriver(driverPlugin);
if (_driver == null)
{
throw Oops.Oh($"创建插件失败");
}
Methods = _pluginService.GetMethod(_driver);
Propertys = _pluginService.GetDriverProperties(_driver);
}
catch (Exception ex)
{
throw Oops.Oh($"创建插件失败:{ex.Message}");
}
}
else
{
throw Oops.Oh($"找不到驱动{driverPlugin?.AssembleName}");
}
//设置插件配置项
SetPluginProperties(_device.DevicePropertys);
return _driver;
}
private void InitDriver(object client)
{
//初始化插件
_driver.Init(_logger, _device, client);
//变量打包
LoadSourceReads(_device.DeviceVariableRunTimes);
}
/// <summary>
/// 设置驱动插件的属性值
/// </summary>
private void SetPluginProperties(List<DependencyProperty> deviceProperties)
{
if (deviceProperties == null) return;
_pluginService.SetDriverProperties(_driver, deviceProperties);
}
#endregion
#region
/// <summary>
/// 是否多个设备共享链路,由外部传入
/// </summary>
public bool IsShareChannel;
/// <summary>
/// 线程开始时执行
/// </summary>
/// <returns></returns>
internal async Task BeforeActionAsync(CancellationToken token, object client = null)
{
try
{
Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime);
if (_device == null)
{
_logger?.LogError(nameof(CollectDeviceRunTime) + "设备不能为null");
isInitSuccess = false;
return;
}
if (_driver == null)
{
_logger?.LogWarning(_device.Name + " - 插件不能为null");
isInitSuccess = false;
return;
}
_logger?.LogInformation($"{_device.Name}采集设备线程开始");
InitDriver(client);
Device.SourceVariableCount = DeviceVariableSourceReads.Count;
Device.MethodVariableCount = DeviceVariableMethodReads.Count;
try
{
if (Device.KeepRun == true)
{
await _driver.BeforStartAsync(token);
Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime);
}
}
catch (Exception ex)
{
_logger?.LogError(ex, _device.Name);
Device.SetDeviceStatus(null, Device.ErrorCount + 1, ex.Message);
}
isInitSuccess = true;
}
catch (Exception ex)
{
_logger?.LogError(ex, _device.Name);
isInitSuccess = false;
Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, 999, ex.Message);
}
}
/// <summary>
/// 结束后
/// </summary>
internal async Task FinishActionAsync()
{
try
{
_logger?.LogInformation($"{_device.Name}采集线程停止中");
await _driver?.AfterStopAsync();
_driver?.SafeDispose();
_logger?.LogInformation($"{_device.Name}采集线程已停止");
}
catch (Exception ex)
{
_logger?.LogError(ex, $"{Device.Name} 释放失败");
}
finally
{
isInitSuccess = false;
GlobalDeviceData.CollectDevices.RemoveWhere(it => it.Id == Device.Id);
easyLock.SafeDispose();
}
}
/// <summary>
/// 初始化
/// </summary>
internal bool Init(CollectDeviceRunTime device)
{
if (device == null)
{
_logger?.LogError(nameof(CollectDeviceRunTime) + "设备不能为null");
return false;
}
try
{
bool isUpDevice = Device != device;
_device = device;
Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime);
_logger = ServiceHelper.Services.GetService<ILoggerFactory>().CreateLogger("采集设备:" + _device.Name);
//更新插件信息
CreatDriver();
//全局数据更新
if (isUpDevice)
{
GlobalDeviceData.CollectDevices.RemoveWhere(it => it.Id == device.Id);
GlobalDeviceData.CollectDevices.Add(device);
}
return true;
}
catch (Exception ex)
{
_logger?.LogError(ex, device.Name);
return false;
}
}
/// <summary>
/// 执行一次读取
/// </summary>
internal async Task<ThreadRunReturn> RunActionAsync(CancellationToken token)
{
try
{
if (_device == null)
{
_logger?.LogError(nameof(CollectDeviceRunTime) + "设备不能为null");
return ThreadRunReturn.Continue;
}
if (_driver == null)
{
_logger?.LogWarning(_device.Name + " - 插件不能为null");
return ThreadRunReturn.Continue;
}
if (Device.KeepRun == false)
{
//采集暂停
return ThreadRunReturn.Continue;
}
if (token.IsCancellationRequested)
return ThreadRunReturn.Break;
int deviceMethodsVariableSuccessNum = 0;
int deviceMethodsVariableFailedNum = 0;
int deviceSourceVariableSuccessNum = 0;
int deviceSourceVariableFailedNum = 0;
//支持读取
if (_driver.IsSupportRequest)
{
foreach (var deviceVariableSourceRead in DeviceVariableSourceReads)
{
if (Device?.KeepRun == false)
{
continue;
}
if (token.IsCancellationRequested)
break;
//连读变量
if (deviceVariableSourceRead.CheckIfRequestAndUpdateTime(SysDateTimeExtensions.CurrentDateTime))
{
try
{
await easyLock.WaitAsync();
var read = await _driver.ReadSourceAsync(deviceVariableSourceRead, token);
if (read != null && read.IsSuccess)
{
_logger?.LogTrace(_device.Name + " - " + " - 采集[" + deviceVariableSourceRead.Address + " - " + deviceVariableSourceRead.Length + "] 数据成功" + read.Content?.ToHexString(" "));
deviceVariableSourceRead.LastSuccess = true;
deviceSourceVariableSuccessNum += 1;
}
else
{
_logger?.LogWarning(_device.Name + " - " + " - 采集[" + deviceVariableSourceRead.Address + " -" + deviceVariableSourceRead.Length + "] 数据失败 - " + read?.Message);
deviceVariableSourceRead.LastSuccess = false;
deviceSourceVariableFailedNum += 1;
Device.SetDeviceStatus(null, Device.ErrorCount + deviceSourceVariableFailedNum, read?.Message);
}
}
finally
{
easyLock.Release();
}
}
}
foreach (var deviceVariableMethodRead in DeviceVariableMethodReads)
{
if (Device?.KeepRun == false)
continue;
if (token.IsCancellationRequested)
break;
//连读变量
if (deviceVariableMethodRead.CheckIfRequestAndUpdateTime(SysDateTimeExtensions.CurrentDateTime))
{
var read = await InvokeMethodAsync(deviceVariableMethodRead, token);
if (read.IsSuccess)
{
_logger?.LogTrace(_device.Name + "执行方法[" + deviceVariableMethodRead.MethodInfo.Name + "] 成功" + read.Content.ToJsonString());
deviceMethodsVariableSuccessNum += 1;
}
else
{
_logger?.LogWarning(_device.Name + "执行方法[" + deviceVariableMethodRead.MethodInfo.Name + "] 失败" + read?.Message);
deviceMethodsVariableFailedNum += 1;
Device.SetDeviceStatus(null, Device.ErrorCount + deviceSourceVariableFailedNum, read.Message);
}
}
}
if (deviceMethodsVariableFailedNum == 0 && deviceSourceVariableFailedNum == 0 && (deviceMethodsVariableSuccessNum != 0 || deviceSourceVariableSuccessNum != 0))
{
//只有成功读取一次,失败次数都会清零
Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, 0);
}
else
{
if (deviceMethodsVariableFailedNum == 0 && deviceSourceVariableFailedNum == 0 && deviceMethodsVariableSuccessNum == 0 && deviceSourceVariableSuccessNum == 0)
{
//这次没有执行读取
//判断是否已连接
if (_driver.IsConnected())
{
//更新设备活动时间
Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime);
}
}
}
}
else //插件自更新设备状态与变量状态
{
//获取设备连接状态
if (_driver.IsConnected())
{
//更新设备活动时间
Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, errorCount: 0);
}
else
{
Device.SetDeviceStatus(errorCount: 999);
}
}
if (token.IsCancellationRequested)
return ThreadRunReturn.Break;
//正常返回None
return ThreadRunReturn.None;
}
catch (TaskCanceledException)
{
return ThreadRunReturn.Break;
}
catch (ObjectDisposedException)
{
return ThreadRunReturn.Break;
}
catch (Exception ex)
{
_logger?.LogWarning(ex, $"采集线程循环异常{_device.Name}");
Device.SetDeviceStatus(null, Device.ErrorCount + 1, ex.Message);
return ThreadRunReturn.None;
}
}
/// <summary>
/// 获取设备变量打包列表/特殊方法列表
/// </summary>
/// <param name="collectVariableRunTimes"></param>
private void LoadSourceReads(List<DeviceVariableRunTime> collectVariableRunTimes)
{
if (_device == null)
{
_logger?.LogError(nameof(CollectDeviceRunTime) + "设备不能为null");
return;
}
if (_driver == null)
{
_logger?.LogWarning(_device.Name + " - 插件不能为null");
return;
}
try
{
//连读打包
var tags = collectVariableRunTimes.Where(it =>
it.ProtectTypeEnum != ProtectTypeEnum.WriteOnly &&
string.IsNullOrEmpty(it.OtherMethod)
&& !string.IsNullOrEmpty(it.VariableAddress)).ToList();
DeviceVariableSourceReads = _driver.LoadSourceRead(tags);
}
catch (Exception ex)
{
throw new($"连读打包失败,请查看变量地址是否正确", ex);
}
var variablesMethod = collectVariableRunTimes.Where(it => !string.IsNullOrEmpty(it.OtherMethod));
{
var tag = variablesMethod.Where(it => it.ProtectTypeEnum != ProtectTypeEnum.WriteOnly);
List<DeviceVariableMethodSource> variablesMethodResult1 = GetMethod(tag, true);
DeviceVariableMethodReads = variablesMethodResult1;
}
{
var tag = variablesMethod.Where(it => it.ProtectTypeEnum != ProtectTypeEnum.ReadOnly);
List<DeviceVariableMethodSource> variablesMethodResult2 = GetMethod(tag, false);
DeviceVariableMethodSources = variablesMethodResult2;
}
List<DeviceVariableMethodSource> GetMethod(IEnumerable<DeviceVariableRunTime> tag, bool isRead)
{
var variablesMethodResult = new List<DeviceVariableMethodSource>();
foreach (var item in tag)
{
var methodResult = new DeviceVariableMethodSource(item.IntervalTime);
var method = Methods.FirstOrDefault(it => it.Name == item.OtherMethod);
if (method != null)
{
methodResult.MethodInfo = new Method(method);
methodResult.MethodStr = item.VariableAddress;
if (isRead)
{
//获取实际执行的参数列表
var ps = methodResult.MethodInfo.Info.GetParameters();
methodResult.MethodObj = new object[ps.Length];
if (!string.IsNullOrEmpty(methodResult.MethodStr))
{
string[] strs = methodResult.MethodStr?.Trim()?.TrimEnd(';').Split(';');
try
{
int index = 0;
for (int i = 0; i < ps.Length; i++)
{
if (typeof(CancellationToken).IsAssignableFrom(ps[i].ParameterType))
{
methodResult.HasTokenObj = true;
}
else
{
if (strs.Length <= index)
continue;
//得到对于的方法参数值
methodResult.MethodObj[i] = methodResult.Converter.ConvertFrom(strs[index], ps[i].ParameterType);
index++;
}
}
}
catch (Exception ex)
{
throw new($"特殊方法初始化失败,请查看变量地址/传入参数是否正确", ex);
}
}
}
methodResult.DeviceVariable = item;
variablesMethodResult.Add(methodResult);
}
}
return variablesMethodResult;
}
}
#endregion
#region
/// <summary>
/// 执行特殊方法
/// </summary>
internal async Task<OperResult<string>> InvokeMethodAsync(DeviceVariableMethodSource deviceVariableMethodSource, bool isRead, string value, CancellationToken token)
{
try
{
await easyLock.WaitAsync();
OperResult<string> result = new();
var method = deviceVariableMethodSource.MethodInfo;
if (method == null)
{
result.ResultCode = ResultCode.Error;
result.Message = $"{deviceVariableMethodSource.DeviceVariable.Name}找不到执行方法{deviceVariableMethodSource.DeviceVariable.OtherMethod}";
return result;
}
else
{
if (!isRead)
{
//获取执行参数
var ps = method.Info.GetParameters();
deviceVariableMethodSource.MethodObj = new object[ps.Length];
if (!string.IsNullOrEmpty(deviceVariableMethodSource.MethodStr) || !string.IsNullOrEmpty(value))
{
string[] strs1 = deviceVariableMethodSource.MethodStr?.Trim()?.TrimEnd(';').Split(';');
string[] strs2 = value?.Trim()?.TrimEnd(';').Split(';');
//通过分号分割,并且合并参数
var strs = strs1?.SpliceArray(strs2);
int index = 0;
for (int i = 0; i < ps.Length; i++)
{
if (typeof(CancellationToken).IsAssignableFrom(ps[i].ParameterType))
{
deviceVariableMethodSource.MethodObj[i] = token;
}
else
{
//得到对于的方法参数值
deviceVariableMethodSource.MethodObj[i] = deviceVariableMethodSource.Converter.ConvertFrom(strs[index], ps[i].ParameterType);
index++;
if (strs.Length <= i)
{
result.ResultCode = ResultCode.Error;
result.Message = $"{deviceVariableMethodSource.DeviceVariable.Name} 执行方法 {deviceVariableMethodSource.DeviceVariable.OtherMethod} 参数不足{deviceVariableMethodSource.MethodStr}";
//参数数量不符
return result;
}
}
}
}
}
else if (deviceVariableMethodSource.HasTokenObj)
{
//获取执行参数
var ps = method.Info.GetParameters();
var newObjs = deviceVariableMethodSource.MethodObj.ToList();
if (!string.IsNullOrEmpty(deviceVariableMethodSource.MethodStr) || !string.IsNullOrEmpty(value))
{
for (int i = 0; i < ps.Length; i++)
{
if (typeof(CancellationToken).IsAssignableFrom(ps[i].ParameterType))
{
newObjs.Insert(i, token);
}
}
}
deviceVariableMethodSource.MethodObj = newObjs.ToArray();
}
try
{
object data = null;
switch (method.TaskType)
{
case TaskReturnType.None:
data = method.Invoke(_driver, deviceVariableMethodSource.MethodObj);
break;
case TaskReturnType.Task:
await method.InvokeAsync(_driver, deviceVariableMethodSource.MethodObj);
break;
case TaskReturnType.TaskObject:
//执行方法
data = await method.InvokeObjectAsync(_driver, deviceVariableMethodSource.MethodObj);
break;
}
result = data?.Adapt<OperResult<string>>();
if (method.HasReturn && result != null && result.IsSuccess)
{
var content = deviceVariableMethodSource.Converter.ConvertTo(result.Content?.ToString()?.Replace($"\0", ""));
if (isRead)
{
var operResult = deviceVariableMethodSource.DeviceVariable.SetValue(content);
if (!operResult.IsSuccess)
{
_logger?.LogWarning(operResult.Message, ToString());
}
}
}
else
{
if (isRead && !result.IsSuccess)
{
var operResult = deviceVariableMethodSource.DeviceVariable.SetValue(null, isOnline: false);
if (!operResult.IsSuccess)
{
_logger?.LogWarning(operResult.Message, ToString());
}
}
}
return result;
}
catch (Exception ex)
{
result.ResultCode = ResultCode.Error;
result.Message = $"{deviceVariableMethodSource.DeviceVariable.Name}执行{deviceVariableMethodSource.DeviceVariable.OtherMethod} 方法失败:{ex.Message}";
return result;
}
}
}
catch (Exception ex)
{
return (new OperResult<string>(ex));
}
finally
{
easyLock.Release();
}
}
/// <summary>
/// 执行变量写入
/// </summary>
/// <returns></returns>
internal async Task<Dictionary<string, OperResult>> InVokeWriteAsync(Dictionary<DeviceVariableRunTime, JToken> writeInfoLists, CancellationToken token)
{
try
{
await easyLock.WaitAsync();
if (IsShareChannel) _driver.InitDataAdapter();
Dictionary<string, OperResult> results = new();
foreach (var deviceVariable in writeInfoLists.Keys)
{
if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions))
{
var jToken = writeInfoLists[deviceVariable];
object rawdata;
if (jToken is JValue jValue)
{
rawdata = jValue.Value;
}
else
{
rawdata = jToken.ToString();
}
object data;
try
{
data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata);
writeInfoLists[deviceVariable] = JToken.FromObject(data);
}
catch (Exception ex)
{
results.Add(deviceVariable.Name, new OperResult(deviceVariable.Name + " 转换写入表达式失败:" + ex.Message));
}
}
}
var result = await _driver.WriteValuesAsync(writeInfoLists.
Where(a => !results.Any(b => b.Key == a.Key.Name)).
ToDictionary(item => item.Key, item => item.Value),
token);
return result;
}
finally
{
easyLock.Release();
}
}
/// <summary>
/// 执行轮询特殊方法,并设置变量值
/// </summary>
private async Task<OperResult<string>> InvokeMethodAsync(DeviceVariableMethodSource deviceVariableMethodRead, CancellationToken token)
{
var data = await InvokeMethodAsync(deviceVariableMethodRead, true, string.Empty, token);
return data;
}
#endregion
}

View File

@@ -1,255 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Microsoft.Extensions.Logging;
using ThingsGateway.Foundation;
using TouchSocket.Core;
namespace ThingsGateway.Application;
/// <summary>
/// 采集设备线程管理
/// </summary>
public class CollectDeviceThread : IAsyncDisposable
{
/// <summary>
/// 链路标识
/// </summary>
public readonly string ChangelID;
/// <summary>
/// CancellationTokenSources
/// </summary>
private ConcurrentList<CancellationTokenSource> StoppingTokens = new();
/// <summary>
/// 线程
/// </summary>
protected Task DeviceTask;
/// <summary>
/// 启停锁
/// </summary>
protected EasyLock easyLock = new();
/// <summary>
/// <inheritdoc/>
/// </summary>
public CollectDeviceThread(string changelID)
{
ChangelID = changelID;
}
/// <summary>
/// 默认等待间隔时间
/// </summary>
public static int CycleInterval { get; } = 10;
/// <summary>
/// 采集设备List在CollectDeviceThread开始前应该初始化内容
/// </summary>
public ConcurrentList<CollectDeviceCore> CollectDeviceCores { get; private set; } = new();
/// <inheritdoc/>
public async ValueTask DisposeAsync()
{
await StopThreadAsync();
easyLock.SafeDispose();
CollectDeviceCores.Clear();
}
/// <summary>
/// 开始采集
/// </summary>
public virtual async Task StartThreadAsync()
{
try
{
await easyLock.WaitAsync();
StoppingTokens.Add(new());
//初始化采集线程
await InitTaskAsync();
if (DeviceTask.Status == TaskStatus.Created)
DeviceTask?.Start();
}
finally
{
easyLock.Release();
}
}
/// <summary>
/// 停止采集前提前取消Token
/// </summary>
public virtual async Task BeforeStopThreadAsync()
{
try
{
await easyLock.WaitAsync();
if (DeviceTask == null)
{
return;
}
foreach (var token in StoppingTokens)
{
token.Cancel();
}
}
finally
{
easyLock.Release();
}
}
/// <summary>
/// 停止采集
/// </summary>
public virtual async Task StopThreadAsync()
{
try
{
await easyLock.WaitAsync();
if (DeviceTask == null)
{
return;
}
foreach (var token in StoppingTokens)
{
token.Cancel();
}
try
{
await DeviceTask.WaitAsync(TimeSpan.FromSeconds(10));
}
catch (ObjectDisposedException)
{
}
catch (TimeoutException)
{
foreach (var device in CollectDeviceCores)
{
device.Logger?.LogInformation($"{device.Device.Name}采集线程停止超时,已强制取消");
}
}
catch (Exception ex)
{
CollectDeviceCores.FirstOrDefault()?.Logger?.LogError(ex, $"{CollectDeviceCores.FirstOrDefault()?.Device?.Name}采集线程停止错误");
}
foreach (CancellationTokenSource token in StoppingTokens)
{
token?.SafeDispose();
}
DeviceTask?.SafeDispose();
DeviceTask = null;
StoppingTokens.Clear();
}
finally
{
easyLock.Release();
}
}
/// <summary>
/// 初始化
/// </summary>
protected async Task InitTaskAsync()
{
var stoppingToken = StoppingTokens.Last().Token;
DeviceTask = await Task.Factory.StartNew(async () =>
{
var channelResult = CollectDeviceCores.FirstOrDefault().Driver.GetShareChannel();
LoggerGroup log = CollectDeviceCores.FirstOrDefault().Driver.LogMessage;
foreach (var device in CollectDeviceCores)
{
if (device.Driver == null)
{
continue;
}
//添加通道报文到每个设备
var data = new EasyLogger(device.Driver.NewMessage) { LogLevel = TouchSocket.Core.LogLevel.Trace };
log.AddLogger(data);
//传入是否共享通道
device.IsShareChannel = CollectDeviceCores.Count > 1;
if (channelResult.IsSuccess)
{
await device.BeforeActionAsync(stoppingToken, channelResult.Content);
}
else
{
await device.BeforeActionAsync(stoppingToken);
}
}
while (!stoppingToken.IsCancellationRequested)
{
foreach (var device in CollectDeviceCores)
{
try
{
if (stoppingToken.IsCancellationRequested)
break;
//初始化成功才能执行
if (device.IsInitSuccess)
{
//如果是共享通道类型,需要每次转换时切换适配器
if (device.IsShareChannel) device.Driver.InitDataAdapter();
var result = await device.RunActionAsync(stoppingToken);
if (result == ThreadRunReturn.None)
{
await Task.Delay(CycleInterval);
}
else if (result == ThreadRunReturn.Continue)
{
await Task.Delay(1000);
}
else if (result == ThreadRunReturn.Break)
{
//当线程返回Break直接跳出循环
break;
}
}
else
{
await Task.Delay(1000, stoppingToken);
}
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
log.Exception(ex);
}
}
}
//注意插件结束函数不能使用取消传播作为条件
foreach (var device in CollectDeviceCores)
{
//如果插件还没释放,执行一次结束函数
if (!device.Driver.DisposedValue)
await device.FinishActionAsync();
}
}
, TaskCreationOptions.LongRunning);
}
}

View File

@@ -1,571 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.FriendlyException;
using Furion.Logging.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ThingsGateway.Application.Extensions;
using ThingsGateway.Foundation;
using TouchSocket.Core;
namespace ThingsGateway.Application;
/// <summary>
/// 设备采集后台服务
/// </summary>
public class CollectDeviceWorker : BackgroundService
{
private readonly ICollectDeviceService _collectDeviceService;
private readonly ILogger<CollectDeviceWorker> _logger;
private readonly PluginSingletonService _pluginService;
/// <inheritdoc/>
public CollectDeviceWorker(ILogger<CollectDeviceWorker> logger, IServiceProvider serviceProvider)
{
ServiceHelper.Services = serviceProvider;
_logger = logger;
_pluginService = ServiceHelper.Services.GetService<PluginSingletonService>();
_collectDeviceService = App.GetService<ICollectDeviceService>();
}
/// <summary>
/// 读取未停止的采集设备List
/// </summary>
public List<CollectDeviceCore> CollectDeviceCores => CollectDeviceThreads
.Where(a => a.CollectDeviceCores.Any(b => b.Device != null))
.SelectMany(a => a.CollectDeviceCores).ToList();
/// <summary>
/// 设备子线程列表
/// </summary>
private ConcurrentList<CollectDeviceThread> CollectDeviceThreads { get; set; } = new();
#region
/// <summary>
/// 全部重启锁
/// </summary>
private readonly EasyLock restartLock = new();
/// <summary>
/// 单个重启锁
/// </summary>
private readonly EasyLock singleRestartLock = new();
/// <summary>
/// 控制设备线程启停
/// </summary>
/// <param name="deviceId">传入0时全部设备都会执行</param>
/// <param name="isStart"></param>
/// <returns></returns>
public void ConfigDeviceThread(long deviceId, bool isStart)
{
if (deviceId == 0)
CollectDeviceCores.ForEach(a => a.PasueThread(isStart));
else
CollectDeviceCores.FirstOrDefault(it => it.DeviceId == deviceId)?.PasueThread(isStart);
}
/// <summary>
/// 重启采集服务
/// </summary>
public async Task RestartDeviceThreadAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
await singleRestartLock.WaitAsync();
//停止其他后台服务
await StopOtherHostService();
//停止全部采集线程
await RemoveAllDeviceThreadAsync();
//创建全部采集线程
await CreatAllDeviceThreadsAsync();
//开始其他后台服务
await StartOtherHostService();
//开始全部采集线程
await StartAllDeviceThreadsAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "重启错误");
}
finally
{
singleRestartLock.Release();
restartLock.Release();
}
}
/// <summary>
/// 更新设备线程,切换为冗余通道
/// </summary>
public async Task UpDeviceRedundantThreadAsync(long devId)
{
try
{
//重启操作在未完全之前直接取消
if (singleRestartLock.IsWaitting)
{
return;
}
await singleRestartLock.WaitAsync();
if (!_stoppingToken.IsCancellationRequested)
{
var devThread = CollectDeviceThreads.FirstOrDefault(it => it.CollectDeviceCores.Any(a => a.DeviceId == devId));
var devCore = devThread.CollectDeviceCores.FirstOrDefault(a => a.DeviceId == devId);
if (devThread == null) { throw Oops.Bah($"更新设备线程失败,不存在{devId}为id的设备"); }
//这里先停止采集,操作会使线程取消,需要重新恢复线程
await devThread.StopThreadAsync();
var dev = devCore.Device;
if (dev.IsRedundant)
{
if (dev.Redundant == RedundantEnum.Standby)
{
var newDev = (await _collectDeviceService.GetCollectDeviceRuntimeAsync(devId)).FirstOrDefault();
if (dev == null)
{
_logger.LogError($"更新设备线程失败,不存在{devId}为id的设备");
}
else
{
dev.DevicePropertys = newDev.DevicePropertys;
dev.Redundant = RedundantEnum.Primary;
_logger?.LogInformation(dev.Name + "切换到主通道");
}
}
else
{
try
{
var Redundantdev = (await _collectDeviceService.GetCollectDeviceRuntimeAsync(dev.RedundantDeviceId)).FirstOrDefault();
if (Redundantdev == null)
{
_logger.LogError($"更新设备线程失败,不存在{devId}为id的设备");
}
else
{
dev.DevicePropertys = Redundantdev.DevicePropertys;
dev.Redundant = RedundantEnum.Standby;
_logger?.LogInformation(dev.Name + "切换到备用通道");
}
}
catch
{
}
}
}
//初始化
devCore.Init(dev);
//线程管理器移除后,如果不存在其他设备,也删除线程管理器
devThread.CollectDeviceCores.Remove(devCore);
if (devThread.CollectDeviceCores.Count == 0)
{
CollectDeviceThreads.Remove(devThread);
}
//需判断是否同一通道
var newDevThread = DeviceThread(devCore);
await newDevThread.StartThreadAsync();
}
}
finally
{
singleRestartLock.Release();
}
}
/// <summary>
/// 更新设备线程
/// </summary>
public async Task UpDeviceThreadAsync(long devId, bool isUpdateDb = true)
{
try
{
//重启操作在未完全之前直接取消
if (singleRestartLock.IsWaitting)
{
return;
}
await singleRestartLock.WaitAsync();
if (!_stoppingToken.IsCancellationRequested)
{
//如果是组态更改过了,需要重新获取变量/设备运行态的值,其他服务需要先停止
if (isUpdateDb)
await StopOtherHostService();
var devThread = CollectDeviceThreads.FirstOrDefault(it => it.CollectDeviceCores.Any(a => a.DeviceId == devId));
var devCore = devThread.CollectDeviceCores.FirstOrDefault(a => a.DeviceId == devId);
if (devThread == null) { throw Oops.Bah($"更新设备线程失败,不存在{devId}为id的设备"); }
//这里先停止采集,操作会使线程取消,需要重新恢复线程
await devThread.StopThreadAsync();
CollectDeviceRunTime dev = isUpdateDb ? (await _collectDeviceService.GetCollectDeviceRuntimeAsync(devId)).FirstOrDefault() : devCore.Device;
if (dev == null)
{
//线程管理器移除后,如果不存在其他设备,也删除线程管理器
devThread.CollectDeviceCores.Remove(devCore);
if (devThread.CollectDeviceCores.Count == 0)
{
CollectDeviceThreads.Remove(devThread);
}
}
else
{
//初始化
devCore.Init(dev);
//线程管理器移除后,如果不存在其他设备,也删除线程管理器
devThread.CollectDeviceCores.Remove(devCore);
if (devThread.CollectDeviceCores.Count == 0)
{
CollectDeviceThreads.Remove(devThread);
}
//需判断是否同一通道
var newDevThread = DeviceThread(devCore);
await newDevThread.StartThreadAsync();
//如果是组态更改过了,需要重新获取变量/设备运行态的值
if (isUpdateDb)
await StartOtherHostService();
}
}
}
finally
{
singleRestartLock.Release();
}
}
#endregion
#region Private
/// <summary>
/// 创建设备采集线程
/// </summary>
/// <returns></returns>
private async Task CreatAllDeviceThreadsAsync()
{
if (!_stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("正在获取采集组态信息");
var collectDeviceRunTimes = (await _collectDeviceService.GetCollectDeviceRuntimeAsync());
_logger.LogInformation("获取采集组态信息完成");
foreach (var collectDeviceRunTime in collectDeviceRunTimes.Where(a => !collectDeviceRunTimes.Any(b => a.Id == b.RedundantDeviceId && b.IsRedundant)))
{
if (!_stoppingToken.IsCancellationRequested)
{
try
{
CollectDeviceCore deviceCollectCore = new();
deviceCollectCore.Init(collectDeviceRunTime);
DeviceThread(deviceCollectCore);
}
catch (Exception ex)
{
_logger.LogError(ex, collectDeviceRunTime.Name);
}
}
}
}
}
/// <summary>
/// 根据通道生成/获取线程管理器
/// </summary>
/// <param name="deviceCollectCore"></param>
/// <returns></returns>
private CollectDeviceThread DeviceThread(CollectDeviceCore deviceCollectCore)
{
if (deviceCollectCore.Driver == null)
return null;
var changelID = deviceCollectCore.Driver.GetChannelID();
if (changelID.IsSuccess)
{
foreach (var collectDeviceThread in CollectDeviceThreads)
{
if (collectDeviceThread.ChangelID == changelID.Content)
{
collectDeviceThread.CollectDeviceCores.Add(deviceCollectCore);
return collectDeviceThread;
}
}
}
return NewDeviceThread(deviceCollectCore, changelID.Content);
CollectDeviceThread NewDeviceThread(CollectDeviceCore deviceCollectCore, string changelID)
{
CollectDeviceThread deviceThread = new(changelID);
deviceThread.CollectDeviceCores.Add(deviceCollectCore);
CollectDeviceThreads.Add(deviceThread);
return deviceThread;
}
}
/// <summary>
/// 删除设备线程,并且释放资源
/// </summary>
private async Task RemoveAllDeviceThreadAsync()
{
await CollectDeviceThreads.ParallelForEachAsync(async (deviceThread, token) =>
{
try
{
await deviceThread.BeforeStopThreadAsync();
}
catch (Exception ex)
{
_logger?.LogError(ex, deviceThread.ToString());
}
}, 10);
await CollectDeviceThreads.ParallelForEachAsync(async (deviceThread, token) =>
{
try
{
await deviceThread.DisposeAsync();
}
catch (Exception ex)
{
_logger?.LogError(ex, deviceThread.ToString());
}
}, 10);
CollectDeviceThreads.Clear();
}
/// <summary>
/// 开始设备采集线程
/// </summary>
/// <returns></returns>
private async Task StartAllDeviceThreadsAsync()
{
if (!_stoppingToken.IsCancellationRequested)
{
foreach (var item in CollectDeviceThreads)
{
if (!_stoppingToken.IsCancellationRequested)
{
await item.StartThreadAsync();
}
}
}
}
/// <summary>
/// 启动其他后台服务
/// </summary>
private async Task StartOtherHostService()
{
var alarmHostService = ServiceHelper.GetBackgroundService<AlarmWorker>();
var historyValueService = ServiceHelper.GetBackgroundService<HistoryValueWorker>();
var uploadDeviceHostService = ServiceHelper.GetBackgroundService<UploadDeviceWorker>();
var memoryVariableWorker = ServiceHelper.GetBackgroundService<MemoryVariableWorker>();
await alarmHostService.StartAsync();
await historyValueService.StartAsync();
await uploadDeviceHostService.StartAsync();
await memoryVariableWorker.StartAsync();
}
/// <summary>
/// 停止其他后台服务
/// </summary>
private async Task StopOtherHostService()
{
var alarmHostService = ServiceHelper.GetBackgroundService<AlarmWorker>();
var historyValueService = ServiceHelper.GetBackgroundService<HistoryValueWorker>();
var uploadDeviceHostService = ServiceHelper.GetBackgroundService<UploadDeviceWorker>();
var memoryVariableWorker = ServiceHelper.GetBackgroundService<MemoryVariableWorker>();
await alarmHostService.StopAsync();
await historyValueService.StopAsync();
await uploadDeviceHostService.StopAsync();
await memoryVariableWorker.StopAsync();
}
#endregion
#region
/// <summary>
/// GetDebugUI
/// </summary>
/// <param name="driverId"></param>
/// <returns></returns>
public Type GetDebugUI(long driverId)
{
var driverPluginService = App.GetService<IDriverPluginService>();
var driverPlugin = driverPluginService.GetDriverPluginById(driverId);
var driver = _pluginService.GetDriver(driverPlugin);
driver?.SafeDispose();
return driver.DriverDebugUIType;
}
/// <summary>
/// 获取设备方法
/// </summary>
/// <param name="devId"></param>
/// <returns></returns>
public List<string> GetDeviceMethods(long devId)
{
var driverPluginService = App.GetService<IDriverPluginService>();
var driverId = _collectDeviceService.GetDeviceById(devId).PluginId;
var driverPlugin = driverPluginService.GetDriverPluginById(driverId);
var driver = (CollectBase)_pluginService.GetDriver(driverPlugin);
var Propertys = _pluginService.GetMethod(driver);
driver?.SafeDispose();
return Propertys.Select(it => it.Name).ToList();
}
/// <summary>
/// 获取设备属性
/// </summary>
/// <param name="driverId"></param>
/// <param name="devId"></param>
/// <returns></returns>
public List<DependencyProperty> GetDevicePropertys(long driverId, long devId = 0)
{
var driverPluginService = App.GetService<IDriverPluginService>();
var driverPlugin = driverPluginService.GetDriverPluginById(driverId);
var driver = _pluginService.GetDriver(driverPlugin);
var Propertys = _pluginService.GetDriverProperties(driver);
if (devId != 0)
{
var devcore = App.GetService<CollectDeviceService>().GetDeviceById(devId);
devcore?.DevicePropertys?.ForEach(it =>
{
var dependencyProperty = Propertys.FirstOrDefault(a => a.PropertyName == it.PropertyName);
if (dependencyProperty != null)
{
dependencyProperty.Value = it.Value;
}
});
}
driver?.SafeDispose();
return Propertys;
}
#endregion
#region worker服务
/// <summary>
/// 在软件关闭时取消
/// </summary>
private CancellationToken _stoppingToken;
/// <inheritdoc/>
public override async Task StartAsync(CancellationToken token)
{
var hardwareInfoService = ServiceHelper.Services.GetService<HardwareInfoService>();
hardwareInfoService.Init();
await base.StartAsync(token);
}
/// <inheritdoc/>
public override async Task StopAsync(CancellationToken token)
{
using var stoppingToken = new CancellationTokenSource();
_stoppingToken = stoppingToken.Token;
stoppingToken.Cancel();
//停止其他后台服务
await StopOtherHostService();
//停止全部采集线程
await RemoveAllDeviceThreadAsync();
await base.StopAsync(token);
}
/// <inheritdoc/>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
//重启采集线程,会启动其他后台服务
await RestartDeviceThreadAsync();
while (!stoppingToken.IsCancellationRequested)
{
try
{
//检测设备采集线程假死
var num = CollectDeviceCores.Count;
for (int i = 0; i < num; i++)
{
CollectDeviceCore devcore = CollectDeviceCores[i];
if (devcore.Device != null)
{
//超过30分钟或者(初始化失败并超过10分钟)会重启
if (
(devcore.Device.ActiveTime != DateTime.MinValue &&
devcore.Device.ActiveTime.AddMinutes(30) <= SysDateTimeExtensions.CurrentDateTime)
|| (devcore.IsInitSuccess == false && devcore.Device.ActiveTime.AddMinutes(10) <= SysDateTimeExtensions.CurrentDateTime)
)
{
//如果线程处于暂停状态,跳过
if (devcore.Device.DeviceStatus == DeviceStatusEnum.Pause)
continue;
//如果初始化失败
if (!devcore.IsInitSuccess)
_logger?.LogWarning(devcore.Device.Name + "初始化失败,重启线程中");
else
_logger?.LogWarning(devcore.Device.Name + "采集线程假死,重启线程中");
//重启线程
await UpDeviceThreadAsync(devcore.Device.Id, false);
break;
}
else
{
_logger?.LogTrace(devcore.Device.Name + "线程检测正常");
}
if (devcore.Device.DeviceStatus == DeviceStatusEnum.OffLine)
{
if (devcore.Device.IsRedundant && _collectDeviceService.GetCacheList(false).Any(a => a.Id == devcore.Device.RedundantDeviceId))
{
await UpDeviceRedundantThreadAsync(devcore.Device.Id);
}
}
}
}
//每5分钟检测一次
await Task.Delay(300000, stoppingToken);
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
_logger.LogError(ex, ToString());
}
}
}
#endregion
}

View File

@@ -1,485 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.Logging.Extensions;
using Mapster;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using ThingsGateway.Admin.Application;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
using ThingsGateway.Foundation.Extension.Generic;
using TouchSocket.Core;
using UAParser;
namespace ThingsGateway.Application;
/// <summary>
/// 实时数据库后台服务
/// </summary>
public class HistoryValueWorker : BackgroundService
{
private readonly GlobalDeviceData _globalDeviceData;
private readonly ILogger<HistoryValueWorker> _logger;
/// <inheritdoc cref="HistoryValueWorker"/>
public HistoryValueWorker(ILogger<HistoryValueWorker> logger)
{
_logger = logger;
_globalDeviceData = ServiceHelper.Services.GetService<GlobalDeviceData>();
}
/// <summary>
/// 服务状态
/// </summary>
public OperResult StatuString { get; set; } = new OperResult("初始化");
private ConcurrentQueue<HistoryValue> ChangeDeviceVariables { get; set; } = new();
private ConcurrentQueue<HistoryValue> DeviceVariables { get; set; } = new();
/// <summary>
/// 获取数据库链接
/// </summary>
/// <returns></returns>
public async Task<OperResult<SqlSugarClient>> GetHisDbAsync()
{
var ConfigService = App.GetService<IConfigService>();
var hisEnable = (await ConfigService.GetByConfigKeyAsync(ThingsGatewayConfigConst.ThingGateway_HisConfig_Base, ThingsGatewayConfigConst.Config_His_Enable))?.ConfigValue?.ToBoolean();
var hisDbType = (await ConfigService.GetByConfigKeyAsync(ThingsGatewayConfigConst.ThingGateway_HisConfig_Base, ThingsGatewayConfigConst.Config_His_DbType))?.ConfigValue;
var hisConnstr = (await ConfigService.GetByConfigKeyAsync(ThingsGatewayConfigConst.ThingGateway_HisConfig_Base, ThingsGatewayConfigConst.Config_His_ConnStr))?.ConfigValue;
if (!(hisEnable == true))
{
return new OperResult<SqlSugarClient>("历史数据已配置为Disable");
}
var configureExternalServices = new ConfigureExternalServices
{
EntityService = (type, column) => // 修改列可空-1、带?问号 2、String类型若没有Required
{
if ((type.PropertyType.IsGenericType && type.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
|| (type.PropertyType == typeof(string) && type.GetCustomAttribute<RequiredAttribute>() == null))
column.IsNullable = true;
},
};
DbType type = DbType.QuestDB;
if (!string.IsNullOrEmpty(hisDbType))
{
if (Enum.TryParse<DbType>(hisDbType, ignoreCase: true, out var result))
{
type = result;
}
else
{
return new OperResult<SqlSugarClient>("数据库类型转换失败");
}
}
var sqlSugarClient = new SqlSugarClient(new ConnectionConfig()
{
ConnectionString = hisConnstr,//连接字符串
DbType = type,//数据库类型
IsAutoCloseConnection = true, //不设成true要手动close
ConfigureExternalServices = configureExternalServices,
});
return OperResult.CreateSuccessResult(sqlSugarClient);
}
#region worker服务
/// <inheritdoc/>
public override async Task StartAsync(CancellationToken token)
{
_logger?.LogInformation("历史服务启动");
await base.StartAsync(token);
}
/// <inheritdoc/>
public override Task StopAsync(CancellationToken token)
{
_logger?.LogInformation("历史服务停止");
return base.StopAsync(token);
}
/// <inheritdoc/>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Delay(5000, stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(60000, stoppingToken);
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
}
}
#endregion
#region core
/// <summary>
/// 循环线程取消标识
/// </summary>
private ConcurrentList<CancellationTokenSource> StoppingTokens = new();
/// <summary>
/// 全部重启锁
/// </summary>
private readonly EasyLock restartLock = new();
private Task HistoryValueTask;
private bool IsExited;
/// <summary>
/// 离线缓存
/// </summary>
protected CacheDb CacheDb { get; set; }
/// <summary>
/// 初始化
/// </summary>
private async Task InitAsync()
{
CacheDb = new("HistoryValueCache");
var stoppingToken = StoppingTokens.Last().Token;
HistoryValueTask = await Task.Factory.StartNew(async () =>
{
_logger?.LogInformation($"历史数据线程开始");
await Task.Yield();//返回线程控制,不再阻塞
try
{
var result = await GetHisDbAsync();
if (!result.IsSuccess)
{
_logger?.LogWarning($"历史数据线程即将退出:" + result.Message);
StatuString = new OperResult($"已退出:{result.Message}");
IsExited = true;
return;
}
else
{
var sqlSugarClient = result.Content;
bool LastIsSuccess = true;
/***创建/更新单个表***/
try
{
await sqlSugarClient.Queryable<HistoryValue>().FirstAsync(stoppingToken);
LastIsSuccess = true;
StatuString = OperResult.CreateSuccessResult();
}
catch (Exception)
{
if (stoppingToken.IsCancellationRequested)
{
IsExited = true;
return;
}
try
{
_logger.LogWarning("连接历史数据表失败,尝试初始化表");
sqlSugarClient.CodeFirst.InitTables(typeof(HistoryValue));
LastIsSuccess = true;
StatuString = OperResult.CreateSuccessResult();
}
catch (Exception ex)
{
LastIsSuccess = false;
StatuString = new OperResult(ex);
_logger.LogWarning(ex, "连接历史数据库失败");
}
}
IsExited = false;
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(500, stoppingToken);
if (stoppingToken.IsCancellationRequested)
break;
//缓存值
var cacheData = await CacheDb.GetCacheData();
var data = cacheData.SelectMany(a => a.CacheStr.FromJsonString<List<HistoryValue>>()).ToList();
try
{
var count = await sqlSugarClient.Insertable(data).ExecuteCommandAsync(stoppingToken);
await CacheDb.DeleteCacheData(cacheData.Select(a => a.Id).ToArray());
}
catch (Exception ex)
{
if (LastIsSuccess)
_logger.LogWarning(ex, "写入历史数据失败");
}
if (stoppingToken.IsCancellationRequested)
break;
var collectList = DeviceVariables.ToListWithDequeue();
if (collectList.Count != 0)
{
////Sql保存
var collecthis = collectList;
int count = 0;
//插入
try
{
count = await sqlSugarClient.Insertable(collecthis).ExecuteCommandAsync(stoppingToken);
LastIsSuccess = true;
}
catch (Exception ex)
{
if (LastIsSuccess)
_logger.LogWarning(ex, "写入历史数据失败");
var cacheDatas = collecthis.ChunkTrivialBetter(500);
foreach (var a in cacheDatas)
{
await CacheDb.AddCacheData("", a.ToJsonString(), 50000);
}
}
}
if (stoppingToken.IsCancellationRequested)
break;
var changeList = ChangeDeviceVariables.ToListWithDequeue();
if (changeList.Count != 0)
{
////Sql保存
var changehis = changeList;
int count = 0;
//插入
try
{
count = await sqlSugarClient.Insertable(changehis).ExecuteCommandAsync(stoppingToken);
LastIsSuccess = true;
}
catch (Exception ex)
{
if (LastIsSuccess)
_logger.LogWarning(ex, "写入历史数据失败");
var cacheDatas = changehis.ChunkTrivialBetter(500);
foreach (var a in cacheDatas)
{
await CacheDb.AddCacheData("", a.ToJsonString(), 50000);
}
}
}
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
if (LastIsSuccess)
_logger?.LogWarning(ex, $"历史数据循环异常");
StatuString = new OperResult(ex);
LastIsSuccess = false;
}
}
}
}
catch (TaskCanceledException)
{
IsExited = true;
}
catch (ObjectDisposedException)
{
IsExited = true;
}
catch (Exception ex)
{
IsExited = true;
_logger?.LogError(ex, $"历史数据循环异常");
}
IsExited = true;
}
, TaskCreationOptions.LongRunning);
}
/// <summary>
/// 重启
/// </summary>
/// <returns></returns>
public async Task RestartAsync()
{
await StopAsync();
await StartAsync();
}
internal async Task StartAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
foreach (var device in _globalDeviceData.CollectDevices)
{
device.DeviceVariableRunTimes?.Where(a => a.HisEnable == true)?.ForEach(v => { v.VariableCollectChange += DeviceVariableCollectChange; });
device.DeviceVariableRunTimes?.Where(a => a.HisEnable == true)?.ForEach(v => { v.VariableValueChange += DeviceVariableValueChange; });
}
StoppingTokens.Add(new());
await InitAsync();
if (HistoryValueTask.Status == TaskStatus.Created)
HistoryValueTask.Start();
IsExited = false;
}
catch (Exception ex)
{
_logger.LogError(ex, "重启错误");
}
finally
{
restartLock.Release();
}
}
internal async Task StopAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
IsExited = true;
foreach (var device in _globalDeviceData.CollectDevices)
{
device.DeviceVariableRunTimes?.Where(a => a.HisEnable == true)?.ForEach(v => { v.VariableCollectChange -= DeviceVariableCollectChange; });
device.DeviceVariableRunTimes?.Where(a => a.HisEnable == true)?.ForEach(v => { v.VariableValueChange -= DeviceVariableValueChange; });
}
foreach (var token in StoppingTokens)
{
token.Cancel();
}
if (HistoryValueTask != null)
{
try
{
_logger?.LogInformation($"历史数据线程停止中");
await HistoryValueTask.WaitAsync(TimeSpan.FromSeconds(10));
_logger?.LogInformation($"历史数据线程已停止");
}
catch (ObjectDisposedException)
{
}
catch (TimeoutException)
{
_logger?.LogWarning($"历史数据线程停止超时,已强制取消");
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "等待历史数据线程停止错误");
}
}
HistoryValueTask?.SafeDispose();
foreach (var token in StoppingTokens)
{
token.SafeDispose();
}
StoppingTokens.Clear();
}
catch (Exception ex)
{
_logger.LogError(ex, "重启错误");
}
finally
{
restartLock.Release();
}
}
private void DeviceVariableCollectChange(DeviceVariableRunTime variable)
{
if (variable.HisType == HisType.Collect && !IsExited)
{
DeviceVariables.Enqueue(variable.Adapt<HistoryValue>());
}
}
private void DeviceVariableValueChange(DeviceVariableRunTime variable)
{
if (variable.HisType == HisType.Change && !IsExited)
{
ChangeDeviceVariables.Enqueue(variable.Adapt<HistoryValue>());
}
}
#endregion
}
/// <summary>
/// <see cref="HistoryValue"/> Master规则
/// </summary>
public class HistoryValueMapper : IRegister
{
/// <inheritdoc/>
public void Register(TypeAdapterConfig config)
{
config.ForType<DeviceVariableRunTime, HistoryValue>()
.Map(dest => dest.Value, (src) => ValueReturn(src))
.Map(dest => dest.CollectTime, (src) => src.CollectTime.ToUniversalTime());//注意sqlsugar插入时无时区直接utc时间
}
private static object ValueReturn(DeviceVariableRunTime src)
{
if (src.Value?.ToString()?.IsBoolValue() == true)
{
if (src.Value.ToBoolean())
{
return 1;
}
else
{
return 0;
}
}
else
{
return src.Value;
}
}
}

View File

@@ -1,255 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.Logging.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ThingsGateway.Foundation;
using TouchSocket.Core;
namespace ThingsGateway.Application;
/// <summary>
/// 实时数据库后台服务
/// </summary>
public class MemoryVariableWorker : BackgroundService
{
private readonly GlobalDeviceData _globalDeviceData;
private readonly ILogger<MemoryVariableWorker> _logger;
/// <inheritdoc cref="MemoryVariableWorker"/>
public MemoryVariableWorker(ILogger<MemoryVariableWorker> logger)
{
_logger = logger;
_globalDeviceData = ServiceHelper.Services.GetService<GlobalDeviceData>();
}
/// <summary>
/// 服务状态
/// </summary>
public OperResult StatuString { get; set; } = new OperResult("初始化");
#region worker服务
/// <inheritdoc/>
public override async Task StartAsync(CancellationToken token)
{
_logger?.LogInformation("中间变量服务启动");
await base.StartAsync(token);
}
/// <inheritdoc/>
public override Task StopAsync(CancellationToken token)
{
_logger?.LogInformation("中间变量服务停止");
return base.StopAsync(token);
}
/// <inheritdoc/>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Delay(5000, stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(60000, stoppingToken);
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
}
}
#endregion
#region core
/// <summary>
/// 循环线程取消标识
/// </summary>
private ConcurrentList<CancellationTokenSource> StoppingTokens = new();
private Task MemoryWorkerTask;
/// <summary>
/// 全部重启锁
/// </summary>
private readonly EasyLock restartLock = new();
/// <summary>
/// 初始化
/// </summary>
public async Task InitAsync()
{
var stoppingToken = StoppingTokens.Last().Token;
MemoryWorkerTask = await Task.Factory.StartNew(async () =>
{
_logger?.LogInformation($"中间变量计算线程开始");
try
{
var variableService = App.GetService<IVariableService>();
var data = await variableService.GetMemoryVariableRuntimeAsync();
_globalDeviceData.MemoryVariables = new(data);
StatuString = OperResult.CreateSuccessResult();
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(500, stoppingToken);
if (stoppingToken.IsCancellationRequested)
break;
var isSuccess = true;
foreach (var item in _globalDeviceData.MemoryVariables)
{
if (!string.IsNullOrEmpty(item.ReadExpressions) && item.ProtectTypeEnum != ProtectTypeEnum.WriteOnly)
{
//变量内部已经做了表达式转换直接赋值0
var operResult = item.SetValue(0);
if (!operResult.IsSuccess)
{
if (StatuString.IsSuccess)
_logger?.LogWarning(operResult.Message, ToString());
isSuccess = false;
StatuString = operResult;
}
}
else
{
}
}
if (isSuccess)
StatuString = OperResult.CreateSuccessResult();
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
_logger?.LogWarning(ex, $"历史数据循环异常");
StatuString = new OperResult(ex);
}
}
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
_logger?.LogError(ex, $"中间变量计算线程循环异常");
}
}
, TaskCreationOptions.LongRunning);
}
internal async Task StartAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
StoppingTokens.Add(new());
//初始化线程
await InitAsync();
if (MemoryWorkerTask.Status == TaskStatus.Created)
MemoryWorkerTask.Start();
}
catch (Exception ex)
{
_logger.LogError(ex, "重启错误");
}
finally
{
restartLock.Release();
}
}
internal async Task StopAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
foreach (var token in StoppingTokens)
{
token.Cancel();
}
if (MemoryWorkerTask != null)
{
try
{
_logger?.LogInformation($"中间变量计算线程停止中");
await MemoryWorkerTask.WaitAsync(TimeSpan.FromSeconds(10));
_logger?.LogInformation($"中间变量计算线程已停止");
}
catch (ObjectDisposedException)
{
}
catch (TimeoutException)
{
_logger?.LogInformation($"中间变量计算线程停止超时,已强制取消");
}
catch (Exception ex)
{
_logger?.LogInformation(ex, "等待线程停止错误");
}
}
MemoryWorkerTask?.SafeDispose();
foreach (var token in StoppingTokens)
{
token.SafeDispose();
}
MemoryWorkerTask = null;
StoppingTokens.Clear();
}
catch (Exception ex)
{
_logger.LogError(ex, "重启错误");
}
finally
{
restartLock.Release();
}
}
#endregion
}

View File

@@ -1,331 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.FriendlyException;
using Furion.Logging.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ThingsGateway.Foundation;
using TouchSocket.Core;
namespace ThingsGateway.Application;
/// <summary>
/// 设备子线程服务
/// </summary>
public class UploadDeviceCore
{
/// <summary>
/// 全局插件服务
/// </summary>
private readonly PluginSingletonService _pluginService;
/// <summary>
/// 读写锁
/// </summary>
private readonly EasyLock easyLock = new();
/// <summary>
/// 当前设备信息
/// </summary>
private UploadDeviceRunTime _device;
/// <summary>
/// 当前的驱动插件实例
/// </summary>
private UpLoadBase _driver;
/// <summary>
/// 日志
/// </summary>
private ILogger _logger;
/// <summary>
/// 是否初始化成功
/// </summary>
private bool isInitSuccess = true;
/// <inheritdoc cref="UploadDeviceCore"/>
public UploadDeviceCore()
{
_pluginService = ServiceHelper.Services.GetService<PluginSingletonService>();
DriverPluginService = App.GetService<IDriverPluginService>();
}
/// <summary>
/// 当前设备
/// </summary>
public UploadDeviceRunTime Device => _device;
/// <summary>
/// 当前设备Id
/// </summary>
public long DeviceId => (long)(_device?.Id.ToLong());
/// <summary>
/// 当前插件
/// </summary>
public UpLoadBase Driver => _driver;
/// <summary>
/// 初始化成功
/// </summary>
public bool IsInitSuccess => isInitSuccess;
/// <summary>
/// 日志
/// </summary>
public ILogger Logger => _logger;
/// <summary>
/// 当前设备全部设备属性,执行初始化后获取正确值
/// </summary>
public List<DependencyProperty> Propertys { get; private set; }
private IDriverPluginService DriverPluginService { get; set; }
/// <summary>
/// 暂停上传
/// </summary>
public void PasueThread(bool keepRun)
{
lock (this)
{
var str = keepRun == false ? "设备线程上传暂停" : "设备线程上传继续";
_logger?.LogInformation($"{str}:{_device.Name}");
this.Device.KeepRun = keepRun;
}
}
#region
/// <summary>
/// 获取插件
/// </summary>
/// <returns></returns>
private UpLoadBase CreatDriver()
{
var driverPlugin = DriverPluginService.GetDriverPluginById(_device.PluginId);
if (driverPlugin != null)
{
try
{
_driver = (UpLoadBase)_pluginService.GetDriver(driverPlugin);
Propertys = _pluginService.GetDriverProperties(_driver);
}
catch (Exception ex)
{
throw Oops.Oh($"创建插件失败:{ex.Message}");
}
}
else
{
throw Oops.Oh($"找不到驱动{driverPlugin.AssembleName}");
}
//设置插件配置项
SetPluginProperties(_device.DevicePropertys);
return _driver;
}
private void InitDriver()
{
//初始化插件
_driver.Init(_logger, _device);
//变量打包
_device.UploadVariableCount = _driver.UploadVariables?.Count ?? 0;
}
/// <summary>
/// 设置驱动插件的属性值
/// </summary>
private void SetPluginProperties(List<DependencyProperty> deviceProperties)
{
if (deviceProperties == null) return;
_pluginService.SetDriverProperties(_driver, deviceProperties);
}
#endregion
#region
/// <summary>
/// 线程开始时执行
/// </summary>
/// <returns></returns>
internal async Task BeforeActionAsync(CancellationToken token)
{
try
{
if (_device == null)
{
_logger?.LogError(nameof(UploadDeviceRunTime) + "设备不能为null");
isInitSuccess = false;
return;
}
if (_driver == null)
{
_logger?.LogWarning(_device.Name + " - 插件不能为null");
isInitSuccess = false;
return;
}
_logger?.LogInformation($"{_device.Name}上传设备线程开始");
InitDriver();
try
{
if (Device.KeepRun == true)
{
await _driver.BeforStartAsync(token);
Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime);
}
}
catch (Exception ex)
{
_logger?.LogError(ex, _device.Name);
Device.SetDeviceStatus(null, Device.ErrorCount + 1, ex.Message);
}
isInitSuccess = true;
}
catch (Exception ex)
{
_logger?.LogError(ex, _device.Name);
isInitSuccess = false;
Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, 999, ex.Message);
}
}
/// <summary>
/// 结束后
/// </summary>
internal async Task FinishActionAsync()
{
try
{
_logger?.LogInformation($"{_device.Name}上传线程停止中");
await _driver?.AfterStopAsync();
_driver?.SafeDispose();
_logger?.LogInformation($"{_device.Name}上传线程已停止");
}
catch (Exception ex)
{
_logger?.LogError(ex, $"{Device.Name} 释放失败");
}
finally
{
isInitSuccess = false;
easyLock.SafeDispose();
}
}
/// <summary>
/// 初始化
/// </summary>
internal bool Init(UploadDeviceRunTime device)
{
if (device == null)
{
_logger?.LogError(nameof(UploadDeviceRunTime) + "设备不能为null");
return false;
}
try
{
bool isUpDevice = Device != device;
_device = device;
_logger = ServiceHelper.Services.GetService<ILoggerFactory>().CreateLogger("上传设备:" + _device.Name);
//更新插件信息
CreatDriver();
return true;
}
catch (Exception ex)
{
_logger?.LogError(ex, device.Name);
return false;
}
}
/// <summary>
/// 执行一次读取
/// </summary>
internal async Task<ThreadRunReturn> RunActionAsync(CancellationToken token)
{
try
{
if (_device == null)
{
_logger?.LogError(nameof(UploadDeviceRunTime) + "设备不能为null");
return ThreadRunReturn.Continue;
}
if (_driver == null)
{
_logger?.LogWarning(_device.Name + " - 插件不能为null");
return ThreadRunReturn.Continue;
}
if (Device.KeepRun == false)
{
//上传暂停
return ThreadRunReturn.Continue;
}
if (token.IsCancellationRequested)
return ThreadRunReturn.Break;
await _driver.ExecuteAsync(token);
//获取设备连接状态
if (_driver.IsConnected())
{
//更新设备活动时间
Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, 0);
}
else
{
Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, 999);
}
if (token.IsCancellationRequested)
return ThreadRunReturn.Break;
//正常返回None
return ThreadRunReturn.None;
}
catch (TaskCanceledException)
{
return ThreadRunReturn.Break;
}
catch (ObjectDisposedException)
{
return ThreadRunReturn.Break;
}
catch (Exception ex)
{
_logger?.LogWarning(ex, $"上传线程循环异常{_device.Name}");
Device.SetDeviceStatus(null, Device.ErrorCount + 1, ex.Message);
return ThreadRunReturn.None;
}
}
#endregion
}

View File

@@ -1,233 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Microsoft.Extensions.Logging;
using ThingsGateway.Foundation;
using TouchSocket.Core;
namespace ThingsGateway.Application;
/// <summary>
/// 上传设备线程管理
/// </summary>
public class UploadDeviceThread : IAsyncDisposable
{
/// <summary>
/// CancellationTokenSources
/// </summary>
private ConcurrentList<CancellationTokenSource> StoppingTokens = new();
/// <summary>
/// 线程
/// </summary>
protected Task DeviceTask;
/// <summary>
/// 启停锁
/// </summary>
protected EasyLock easyLock = new();
/// <summary>
/// 默认等待间隔时间
/// </summary>
public static int CycleInterval { get; } = 10;
/// <summary>
/// 上传设备List在CollectDeviceThread开始前应该初始化内容
/// </summary>
public ConcurrentList<UploadDeviceCore> UploadDeviceCores { get; private set; } = new();
/// <inheritdoc/>
public async ValueTask DisposeAsync()
{
await StopThreadAsync();
easyLock.SafeDispose();
UploadDeviceCores.Clear();
}
/// <summary>
/// 开始上传
/// </summary>
public virtual async Task StartThreadAsync()
{
try
{
await easyLock.WaitAsync();
StoppingTokens.Add(new());
//初始化上传线程
await InitTaskAsync();
if (DeviceTask.Status == TaskStatus.Created)
DeviceTask?.Start();
}
finally
{
easyLock.Release();
}
}
/// <summary>
/// 停止采集前提前取消Token
/// </summary>
public virtual async Task BeforeStopThreadAsync()
{
try
{
await easyLock.WaitAsync();
if (DeviceTask == null)
{
return;
}
foreach (var token in StoppingTokens)
{
token.Cancel();
}
}
finally
{
easyLock.Release();
}
}
/// <summary>
/// 停止上传
/// </summary>
public virtual async Task StopThreadAsync()
{
try
{
await easyLock.WaitAsync();
if (DeviceTask == null)
{
return;
}
foreach (var token in StoppingTokens)
{
token.Cancel();
}
try
{
await DeviceTask.WaitAsync(TimeSpan.FromSeconds(10));
}
catch (ObjectDisposedException)
{
}
catch (TimeoutException)
{
foreach (var device in UploadDeviceCores)
{
device.Logger?.LogInformation($"{device.Device.Name}上传线程停止超时,已强制取消");
}
}
catch (Exception ex)
{
UploadDeviceCores.FirstOrDefault()?.Logger?.LogError(ex, $"{UploadDeviceCores.FirstOrDefault()?.Device?.Name}上传线程停止错误");
}
foreach (CancellationTokenSource token in StoppingTokens)
{
token?.SafeDispose();
}
DeviceTask?.SafeDispose();
DeviceTask = null;
StoppingTokens.Clear();
}
finally
{
easyLock.Release();
}
}
/// <summary>
/// 初始化
/// </summary>
protected async Task InitTaskAsync()
{
var stoppingToken = StoppingTokens.Last().Token;
DeviceTask = await Task.Factory.StartNew(async () =>
{
LoggerGroup log = UploadDeviceCores.FirstOrDefault().Driver.LogMessage;
foreach (var device in UploadDeviceCores)
{
if (device.Driver == null)
{
continue;
}
//添加通道报文到每个设备
var data = new EasyLogger(device.Driver.NewMessage) { LogLevel = TouchSocket.Core.LogLevel.Trace };
log.AddLogger(data);
await device.BeforeActionAsync(stoppingToken);
}
while (!stoppingToken.IsCancellationRequested)
{
foreach (var device in UploadDeviceCores)
{
try
{
if (stoppingToken.IsCancellationRequested)
break;
//初始化成功才能执行
if (device.IsInitSuccess)
{
var result = await device.RunActionAsync(stoppingToken);
if (result == ThreadRunReturn.None)
{
await Task.Delay(CycleInterval);
}
else if (result == ThreadRunReturn.Continue)
{
await Task.Delay(1000);
}
else if (result == ThreadRunReturn.Break)
{
//当线程返回Break直接跳出循环
break;
}
}
else
{
await Task.Delay(1000);
}
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
log.Exception(ex);
}
}
}
//注意插件结束函数不能使用取消传播作为条件
foreach (var device in UploadDeviceCores)
{
//如果插件还没释放,执行一次结束函数
if (!device.Driver.DisposedValue)
await device.FinishActionAsync();
}
}
, TaskCreationOptions.LongRunning);
}
}

View File

@@ -1,436 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.FriendlyException;
using Furion.Logging.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ThingsGateway.Application.Extensions;
using ThingsGateway.Foundation;
using TouchSocket.Core;
namespace ThingsGateway.Application;
/// <summary>
/// 设备上传后台服务
/// </summary>
public class UploadDeviceWorker : BackgroundService
{
private readonly ILogger<UploadDeviceWorker> _logger;
private readonly PluginSingletonService _pluginService;
private readonly IUploadDeviceService _uploadDeviceService;
/// <inheritdoc cref="UploadDeviceWorker"/>
public UploadDeviceWorker(ILogger<UploadDeviceWorker> logger)
{
_logger = logger;
_pluginService = ServiceHelper.Services.GetService<PluginSingletonService>();
_uploadDeviceService = App.GetService<IUploadDeviceService>();
}
/// <summary>
/// 上传设备List
/// </summary>
public List<UploadDeviceCore> UploadDeviceCores => UploadDeviceThreads
.Where(a => a.UploadDeviceCores.Any(b => b.Device != null))
.SelectMany(a => a.UploadDeviceCores).ToList();
/// <summary>
/// 全部设备子线程
/// </summary>
private ConcurrentList<UploadDeviceThread> UploadDeviceThreads { get; set; } = new();
#region
/// <summary>
/// 全部重启锁
/// </summary>
private readonly EasyLock restartLock = new();
/// <summary>
/// 单个重启锁
/// </summary>
private readonly EasyLock singleRestartLock = new();
/// <summary>
/// 控制设备线程启停
/// </summary>
public void ConfigDeviceThread(long deviceId, bool isStart)
{
if (deviceId == 0)
UploadDeviceCores.ForEach(it => it.PasueThread(isStart));
else
UploadDeviceCores.FirstOrDefault(it => it.DeviceId == deviceId)?.PasueThread(isStart);
}
/// <summary>
/// 开始
/// </summary>
public async Task StartAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
await singleRestartLock.WaitAsync();
CreatAllDeviceThreads();
await StartAllDeviceThreadsAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "重启错误");
}
finally
{
singleRestartLock.Release();
restartLock.Release();
}
}
/// <summary>
/// 停止
/// </summary>
public async Task StopAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
await singleRestartLock.WaitAsync();
await RemoveAllDeviceThreadAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "重启错误");
}
finally
{
singleRestartLock.Release();
restartLock.Release();
}
}
/// <summary>
/// 更新设备线程
/// </summary>
public async Task UpDeviceThreadAsync(long devId)
{
try
{
//重启操作在未完全之前直接取消
if (singleRestartLock.IsWaitting)
{
return;
}
await singleRestartLock.WaitAsync();
if (!_stoppingToken.IsCancellationRequested)
{
var devThread = UploadDeviceThreads.FirstOrDefault(it => it.UploadDeviceCores.Any(a => a.DeviceId == devId));
var devCore = devThread.UploadDeviceCores.FirstOrDefault(a => a.DeviceId == devId);
if (devThread == null) { throw Oops.Bah($"更新设备线程失败,不存在{devId}为id的设备"); }
//这里先停止上传,操作会使线程取消,需要重新恢复线程
await devThread.StopThreadAsync();
var dev = _uploadDeviceService.GetUploadDeviceRuntime(devId).FirstOrDefault();
if (dev == null)
{
//线程管理器移除后,如果不存在其他设备,也删除线程管理器
devThread.UploadDeviceCores.Remove(devCore);
if (devThread.UploadDeviceCores.Count == 0)
{
UploadDeviceThreads.Remove(devThread);
}
}
else
{
//初始化
devCore.Init(dev);
//线程管理器移除后,如果不存在其他设备,也删除线程管理器
devThread.UploadDeviceCores.Remove(devCore);
if (devThread.UploadDeviceCores.Count == 0)
{
UploadDeviceThreads.Remove(devThread);
}
//需判断是否同一通道
var newDevThread = DeviceThread(devCore);
await newDevThread.StartThreadAsync();
}
}
}
finally
{
singleRestartLock.Release();
}
}
#endregion
#region
/// <summary>
/// 创建设备上传线程
/// </summary>
/// <returns></returns>
private void CreatAllDeviceThreads()
{
if (!_stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("正在获取采集组态信息");
var collectDeviceRunTimes = (_uploadDeviceService.GetUploadDeviceRuntime());
_logger.LogInformation("获取采集组态信息完成");
foreach (var collectDeviceRunTime in collectDeviceRunTimes)
{
if (!_stoppingToken.IsCancellationRequested)
{
try
{
UploadDeviceCore deviceCollectCore = new();
deviceCollectCore.Init(collectDeviceRunTime);
DeviceThread(deviceCollectCore);
}
catch (Exception ex)
{
_logger.LogError(ex, collectDeviceRunTime.Name);
}
}
}
}
}
private UploadDeviceThread DeviceThread(UploadDeviceCore deviceUploadCore)
{
UploadDeviceThread deviceThread = new();
deviceThread.UploadDeviceCores.Add(deviceUploadCore);
UploadDeviceThreads.Add(deviceThread);
return deviceThread;
}
/// <summary>
/// 删除设备线程,并且释放资源
/// </summary>
private async Task RemoveAllDeviceThreadAsync()
{
await UploadDeviceThreads.ParallelForEachAsync(async (deviceThread, token) =>
{
try
{
await deviceThread.BeforeStopThreadAsync();
}
catch (Exception ex)
{
_logger?.LogError(ex, deviceThread.ToString());
}
}, 10);
await UploadDeviceThreads.ParallelForEachAsync(async (deviceThread, token) =>
{
try
{
await deviceThread.DisposeAsync();
}
catch (Exception ex)
{
_logger?.LogError(ex, deviceThread.ToString());
}
}, 10);
UploadDeviceThreads.Clear();
}
/// <summary>
/// 开始设备上传线程
/// </summary>
/// <returns></returns>
private async Task StartAllDeviceThreadsAsync()
{
if (!_stoppingToken.IsCancellationRequested)
{
foreach (var item in UploadDeviceThreads)
{
if (!_stoppingToken.IsCancellationRequested)
{
await item.StartThreadAsync();
}
}
}
}
#endregion
#region
/// <summary>
/// 获取设备属性
/// </summary>
/// <param name="driverId"></param>
/// <param name="devId"></param>
/// <returns></returns>
public List<DependencyProperty> GetDevicePropertys(long driverId, long devId = 0)
{
var driverPluginService = App.GetService<IDriverPluginService>();
var driverPlugin = driverPluginService.GetDriverPluginById(driverId);
var driver = _pluginService.GetDriver(driverPlugin);
var Propertys = _pluginService.GetDriverProperties(driver);
if (devId != 0)
{
var devcore = App.GetService<UploadDeviceService>().GetDeviceById(devId);
devcore?.DevicePropertys?.ForEach(it =>
{
var dependencyProperty = Propertys.FirstOrDefault(a => a.PropertyName == it.PropertyName);
if (dependencyProperty != null)
{
dependencyProperty.Value = it.Value;
}
});
}
driver?.SafeDispose();
return Propertys;
}
/// <summary>
/// 获取变量上传属性
/// </summary>
/// <param name="driverId"></param>
/// <param name="dependencyProperties"></param>
/// <returns></returns>
public List<DependencyProperty> GetVariablePropertys(long driverId, List<DependencyProperty> dependencyProperties = null)
{
var driverPluginService = App.GetService<IDriverPluginService>();
var driverPlugin = driverPluginService.GetDriverPluginById(driverId);
var driver = (UpLoadBase)_pluginService.GetDriver(driverPlugin);
var Propertys = _pluginService.GetDriverVariableProperties(driver);
dependencyProperties?.ForEach(it =>
{
var dependencyProperty = Propertys.FirstOrDefault(a => a.PropertyName == it.PropertyName);
if (dependencyProperty != null)
{
dependencyProperty.Value = it.Value;
}
});
driver?.SafeDispose();
return Propertys;
}
#endregion
#region worker服务
/// <summary>
/// 在软件关闭时取消
/// </summary>
private CancellationToken _stoppingToken;
/// <inheritdoc/>
public override async Task StartAsync(CancellationToken token)
{
await base.StartAsync(token);
}
/// <inheritdoc/>
public override async Task StopAsync(CancellationToken token)
{
using var stoppingToken = new CancellationTokenSource();
_stoppingToken = stoppingToken.Token;
stoppingToken.Cancel();
await base.StopAsync(token);
}
/// <inheritdoc/>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
//这里不采用CancellationToken控制子线程直接循环保持结束时调用子设备线程Dispose
//检测设备上传线程假死
var num = UploadDeviceCores.Count;
for (int i = 0; i < num; i++)
{
UploadDeviceCore devcore = UploadDeviceCores[i];
if (devcore.Device != null)
{
//超过30分钟或者(初始化失败并超过10分钟)会重启
if (
(devcore.Device.ActiveTime != DateTime.MinValue
&& devcore.Device.ActiveTime.AddMinutes(30) <= SysDateTimeExtensions.CurrentDateTime)
|| (devcore.IsInitSuccess == false && devcore.Device.ActiveTime.AddMinutes(10) <= SysDateTimeExtensions.CurrentDateTime)
)
{
//如果线程处于暂停状态,跳过
if (devcore.Device.DeviceStatus == DeviceStatusEnum.Pause)
continue;
//如果初始化失败
if (!devcore.IsInitSuccess)
_logger?.LogWarning(devcore.Device.Name + "初始化失败,重启线程中");
else
_logger?.LogWarning(devcore.Device.Name + "上传线程假死,重启线程中");
//重启线程
await UpDeviceThreadAsync(devcore.Device.Id);
break;
}
else
{
_logger?.LogTrace(devcore.Device.Name + "线程检测正常");
}
}
}
//每5分钟检测一次
await Task.Delay(300000, stoppingToken);
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
_logger.LogError(ex, ToString());
}
}
}
#endregion
}

View File

@@ -1,137 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@using ThingsGateway.Admin.Blazor.Core;
@using ThingsGateway.Admin.Core;
@using BlazorComponent;
@using Microsoft.AspNetCore.Components.Web;
@using Microsoft.JSInterop;
@using ThingsGateway.Foundation;
@using ThingsGateway.Foundation.Extension;
@using ThingsGateway.Foundation.Serial;
@using Masa.Blazor;
@using ThingsGateway.Application;
@namespace ThingsGateway.Blazor
@inject UserResoures UserResoures;
@inherits DriverDebugUIBase
<MCard Flat Elevation="0">
@{
switch (Channel)
{
case ChannelEnum.TcpClientEx:
<TcpClientPage @ref=TcpClientPage></TcpClientPage>
break;
case ChannelEnum.SerialPort:
<SerialSessionPage @ref=SerialSessionPage></SerialSessionPage>
break;
case ChannelEnum.UdpSession:
<UdpSessionPage @ref=UdpSessionPage></UdpSessionPage>
break;
case ChannelEnum.TcpServer:
<TcpServerPage @ref=TcpServerPage></TcpServerPage>
break;
}
}
@if (Plc != null && ChildContent != null)
{
@ChildContent
}
<MCard Class="pa-4" Flat Elevation="0">
<MRow Class="my-1" NoGutters>
<MCol Md="4">
<MCard Flat Elevation="0">
@if (OtherContent!=null)
{
@OtherContent
}
else
{
<MCol Class="my-1 py-1">
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MTextField Class="mx-1 my-1" Label="变量地址" @attributes="@tip.Attrs" Dense Outlined HideDetails="@("auto")" @bind-Value=@Address />
</ActivatorContent>
<ChildContent>
<span style="white-space: pre-wrap;">@Plc?.GetAddressDescription()</span>
</ChildContent>
</MTooltip>
<MSelect Class="mx-1 my-1" Style="max-width:200px" @bind-Value="DataTypeEnum" Outlined Label="数据类型"
Items=@(typeof(DataTypeEnum).GetEnumList())
MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })"
ItemText=@((u) =>u.des)
ItemValue=@(u =>(DataTypeEnum)u.value)
HideDetails=@("auto") Height="30"
Dense>
</MSelect>
<MButton Class="mx-1 my-1" Color="primary" OnClick="ReadAsync">
读取
</MButton>
<MTextarea Class="mx-1 mt-3 my-1" Label="值" Dense Outlined HideDetails="@("auto")" @bind-Value=@WriteValue />
<MButton Class="mx-1 my-1" Color="primary" OnClick="WriteAsync">
写入
</MButton>
</MCol>
}
</MCard>
</MCol>
<MCol Md="8">
<MCard Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+300}px)") Style="overflow-y:auto;width:100%" Elevation="0" Flat Class="ml-4">
<MCardActions>
输出日志
<MSpacer></MSpacer>
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Loading=isDownExport Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small
OnClick=@(async()=>
{
await DownDeviceMessageExportAsync(Messages.Select(a=>a.message));
}
)>
<MIcon>mdi-export</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>导出</span>
</ChildContent>
</MTooltip>
</MCardActions>
@{
var item = Messages;
<MRow Class="ml-2 mr-2 d-flex" NoGutters>
<MVirtualScroll Context="itemMessage" Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+400}px)") OverscanCount=2 ItemSize="100" Items="item">
<ItemContent>
<div title=@itemMessage.message class=@(itemMessage.level<Microsoft.Extensions.Logging.LogLevel.Information?UserResoures.IsDark? " while--text ":"black--text":itemMessage.level>=Microsoft.Extensions.Logging.LogLevel.Warning?" red--text ":"green--text ") style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;">
@itemMessage.message
</div>
</ItemContent>
</MVirtualScroll>
</MRow>
}
</MCard>
</MCol>
</MRow>
</MCard>
</MCard>

View File

@@ -1,109 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Microsoft.AspNetCore.Components;
using ThingsGateway.Application;
using TouchSocket.Core;
namespace ThingsGateway.Blazor;
/// <summary>
/// <inheritdoc/>
/// </summary>
public enum ChannelEnum
{
/// <inheritdoc/>
None = 0,
/// <inheritdoc/>
TcpClientEx = 1,
/// <inheritdoc/>
SerialPort = 2,
/// <inheritdoc/>
UdpSession = 3,
/// <inheritdoc/>
TcpServer = 4,
}
/// <inheritdoc/>
public partial class DefalutDebugDriverPage : DriverDebugUIBase
{
/// <summary>
/// SerialSessionPage
/// </summary>
public SerialSessionPage SerialSessionPage;
/// <summary>
/// TcpClientPage
/// </summary>
public TcpClientPage TcpClientPage;
/// <summary>
/// TcpServerPage
/// </summary>
public TcpServerPage TcpServerPage;
/// <summary>
/// UdpSessionPage
/// </summary>
public UdpSessionPage UdpSessionPage;
/// <summary>
/// 选择1-TCPCLIENT2-串口3-UDP4-TCPServer
/// </summary>
[Parameter]
public ChannelEnum Channel { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
public override ThingsGateway.Foundation.IReadWriteDevice Plc { get; set; }
/// <summary>
/// 模板
/// </summary>
[Parameter]
public RenderFragment ChildContent { get; set; }
/// <summary>
/// 自定义模板
/// </summary>
[Parameter]
public RenderFragment OtherContent { get; set; }
/// <inheritdoc/>
public override void Dispose()
{
Plc?.SafeDispose();
TcpClientPage?.SafeDispose();
SerialSessionPage?.SafeDispose();
TcpServerPage?.SafeDispose();
UdpSessionPage?.SafeDispose();
base.Dispose();
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="firstRender"></param>
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
if (TcpClientPage != null)
TcpClientPage.LogAction = LogOut;
if (SerialSessionPage != null)
SerialSessionPage.LogAction = LogOut;
if (TcpServerPage != null)
TcpServerPage.LogAction = LogOut;
if (UdpSessionPage != null)
UdpSessionPage.LogAction = LogOut;
//载入配置
StateHasChanged();
}
base.OnAfterRender(firstRender);
}
}

View File

@@ -1,255 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
using System.IO;
using System.Threading;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Admin.Core;
using ThingsGateway.Application.Extensions;
using ThingsGateway.Foundation;
namespace ThingsGateway.Application;
/// <summary>
/// 调试UI
/// </summary>
public abstract class DriverDebugUIBase : ComponentBase, IDisposable
{
/// <summary>
/// 导出提示
/// </summary>
public bool isDownExport;
/// <summary>
/// 日志缓存
/// </summary>
public ConcurrentLinkedList<(LogLevel level, string message)> Messages = new();
IJSObjectReference _helper;
readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(1));
/// <summary>
/// 默认读写设备
/// </summary>
public virtual IReadWriteDevice Plc { get; set; }
/// <summary>
/// 变量地址
/// </summary>
public virtual string Address { get; set; } = "40001";
/// <summary>
/// 数据类型
/// </summary>
protected virtual DataTypeEnum DataTypeEnum { get; set; } = DataTypeEnum.Int16;
/// <inheritdoc/>
[Inject]
protected IJSRuntime JS { get; set; }
/// <summary>
/// 写入值
/// </summary>
public virtual string WriteValue { get; set; }
[Inject]
private InitTimezone InitTimezone { get; set; }
/// <inheritdoc/>
public virtual void Dispose()
{
_periodicTimer?.Dispose();
}
/// <inheritdoc/>
public virtual async Task ReadAsync()
{
try
{
var data = await Plc.ReadAsync(Address, DataTypeEnum.GetSystemType());
if (data.IsSuccess)
{
Messages.Add((LogLevel.Information, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - 对应类型值:" + data.Content));
}
else
{
Messages.Add((LogLevel.Error, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + data.Message));
}
}
catch (Exception ex)
{
Messages.Add((LogLevel.Warning, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + "错误:" + ex.Message));
}
}
/// <inheritdoc/>
public virtual async Task WriteAsync()
{
try
{
var data = await Plc.WriteAsync(Address, DataTypeEnum.GetSystemType(), WriteValue);
if (data.IsSuccess)
{
Messages.Add((LogLevel.Information, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + data.Message));
}
else
{
Messages.Add((LogLevel.Warning, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + data.Message));
}
}
catch (Exception ex)
{
Messages.Add((LogLevel.Error, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + "写入前失败:" + ex.Message));
}
}
/// <summary>
/// 导入设备
/// </summary>
/// <returns></returns>
public async Task DeviceImportAsync(CollectDevice data)
{
try
{
isDownExport = true;
StateHasChanged();
await App.GetService<CollectDeviceService>().AddAsync(data);
}
finally
{
isDownExport = false;
}
}
/// <summary>
/// 导入变量
/// </summary>
/// <returns></returns>
public async Task DeviceVariableImportAsync(List<DeviceVariable> data)
{
try
{
isDownExport = true;
StateHasChanged();
await App.GetService<VariableService>().AddBatchAsync(data);
}
finally
{
isDownExport = false;
}
}
/// <summary>
/// 导出
/// </summary>
/// <param name="values"></param>
/// <returns></returns>
public async Task DownDeviceMessageExportAsync(IEnumerable<string> values)
{
try
{
isDownExport = true;
StateHasChanged();
using var memoryStream = new MemoryStream();
using StreamWriter writer = new(memoryStream);
foreach (var item in values)
{
writer.WriteLine(item);
}
writer.Flush();
memoryStream.Seek(0, SeekOrigin.Begin);
using var streamRef = new DotNetStreamReference(stream: memoryStream);
_helper ??= await JS.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Admin.Blazor.Core/js/downloadFileFromStream.js");
await _helper.InvokeVoidAsync("downloadFileFromStream", $"报文导出{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.txt", streamRef);
}
finally
{
isDownExport = false;
}
}
/// <summary>
/// 导出到excel
/// </summary>
/// <returns></returns>
public async Task DownDeviceExportAsync(CollectDevice data)
{
try
{
isDownExport = true;
StateHasChanged();
using var memoryStream = await App.GetService<CollectDeviceService>().ExportFileAsync(new List<CollectDevice>() { data });
using var streamRef = new DotNetStreamReference(stream: memoryStream);
_helper ??= await JS.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Admin.Blazor.Core/js/downloadFileFromStream.js");
await _helper.InvokeVoidAsync("downloadFileFromStream", $"设备导出{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx", streamRef);
}
finally
{
isDownExport = false;
}
}
/// <summary>
/// 导出到excel
/// </summary>
/// <returns></returns>
public async Task DownDeviceVariableExportAsync(List<DeviceVariable> data, string devName)
{
try
{
isDownExport = true;
StateHasChanged();
using var memoryStream = await App.GetService<VariableService>().ExportFileAsync(data, devName);
using var streamRef = new DotNetStreamReference(stream: memoryStream);
_helper ??= await JS.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Admin.Blazor.Core/js/downloadFileFromStream.js");
await _helper.InvokeVoidAsync("downloadFileFromStream", $"变量导出{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx", streamRef);
}
finally
{
isDownExport = false;
}
}
/// <inheritdoc/>
public void LogOut(TouchSocket.Core.LogLevel logLevel, object source, string message, Exception exception)
{
Messages.Add(((LogLevel)logLevel, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + message + (exception != null ? exception.Message : "")));
if (Messages.Count > 2500)
{
Messages.Clear();
}
}
/// <inheritdoc/>
protected override void OnInitialized()
{
_ = RunTimerAsync();
base.OnInitialized();
}
private async Task RunTimerAsync()
{
while (await _periodicTimer.WaitForNextTickAsync())
{
await InvokeAsync(StateHasChanged);
}
}
}

View File

@@ -1,304 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/gatewayconfig/collectdevice"
@namespace ThingsGateway.Blazor
@using System.Linq.Expressions;
@using BlazorComponent;
@using Furion;
@using Mapster;
@using Masa.Blazor
@using Masa.Blazor.Presets;
@using System.IO;
@using Microsoft.AspNetCore.Authorization;
@using ThingsGateway.Admin.Blazor.Core;
@using ThingsGateway.Admin.Blazor;
@using ThingsGateway.Application;
@attribute [Authorize]
@inherits BaseComponentBase
@inject UserResoures UserResoures
@layout MainLayout
@using ThingsGateway.Admin.Core;
@if (IsMobile)
{
@GetAppDataTable()
}
else
{
<MRow>
<MCol Md=2 Cols="12">
<MCard Class="ma-2" Height=@("100%")>
<MCardTitle>
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="mx-2 my-1" @bind-Value="_searchName"
Outlined Label=@typeof(CollectDevice).GetDescription(nameof(CollectDevice.DeviceGroup)) />
</MCardTitle>
<MTreeview
Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+100}px);overflow-y:auto")
Dense TItem="string" TKey="string" ActiveChanged=@(async a=>
{
if(search.DeviceGroup!=a.FirstOrDefault())
{
search.DeviceGroup=a.FirstOrDefault();
await DatatableQueryAsync();
}
} )
Items="_deviceGroups" ItemText="r=>r" ItemChildren="r=>null"
Search="@_searchName"
Activatable ItemKey=@(r=>r)>
<LabelContent>
<span title=@context.Item>
@context.Item
</span>
</LabelContent>
</MTreeview>
</MCard>
</MCol>
<MCol Md=10 Cols="12">
@GetAppDataTable()
</MCol>
</MRow>
}
<ImportExcel @ref=ImportExcel Import="SaveDeviceImportAsync" Preview="DeviceImportAsync" />
@code {
RenderFragment GetAppDataTable()
{
RenderFragment renderFragment =
@<AppDataTable @ref="_datatable"
StyleString=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+10}px)")
TItem="CollectDevice" SearchItem="CollectDevicePageInput"
AddItem="CollectDeviceAddInput" EditItem="CollectDeviceEditInput"
IsMenuOperTemplate=false SearchModel="@search"
QueryCallAsync="QueryCallAsync" AddCallAsync="AddCallAsync"
EditCallAsync="EditCallAsync" DeleteCallAsync="DeleteCallAsync"
IsShowDetailButton
IsShowQueryButton
IsShowAddButton=@UserResoures.IsHasButtonWithRole("gatewaycollectdeviceadd")
IsShowDeleteButton=@UserResoures.IsHasButtonWithRole("gatewaycollectdevicedelete")
IsShowEditButton=@UserResoures.IsHasButtonWithRole("gatewaycollectdeviceedit")>
<AddTemplate>
@GetRenderFragment(context)
</AddTemplate>
<EditTemplate>
@GetRenderFragment(context)
</EditTemplate>
<SearchTemplate>
<MTextField Dense
Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.Name"
Clearable
Outlined
Label=@context.Description(x => x.Name) />
<MTextField Dense
Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.PluginName"
Clearable
Outlined
Label=@context.Description(x => x.PluginName) />
<MTextField Dense
Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.DeviceGroup"
Clearable
Outlined
Label=@context.Description(x => x.DeviceGroup) />
</SearchTemplate>
<OtherToolbarTemplate>
<MMenu OffsetY
Context="menu">
<ActivatorContent>
<MButton @attributes="@menu.Attrs" Color="primary"
Class="my-1 mx-2 ">
复制
<AppChevronDown></AppChevronDown>
</MButton>
</ActivatorContent>
<ChildContent>
<MList>
<MListItem OnClick="()=>CopyDeviceAsync(context)">复制设备</MListItem>
<MListItem OnClick="()=>CopyDevAndVarAsync(context)">复制设备及设备下变量</MListItem>
</MList>
</ChildContent>
</MMenu>
<MMenu OffsetY
Context="menu">
<ActivatorContent>
<MButton @attributes="@menu.Attrs" Color="primary"
Class="my-1 mx-2 ">
导出
<AppChevronDown></AppChevronDown>
</MButton>
</ActivatorContent>
<ChildContent>
<MList>
<MListItem OnClick="()=>DownExportAsync()">导出全部</MListItem>
<MListItem OnClick="()=>DownExportAsync(search)">导出搜索项</MListItem>
</MList>
</ChildContent>
</MMenu>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaycollectdeviceedit")) Class="my-1 mx-2" OnClick="()=>{ ImportExcel.Step=1; ImportExcel.IsShowImport=true;}" Color="primary">
导入
</MButton>
</OtherToolbarTemplate>
<ItemColTemplate>
@switch (context.Header.Value)
{
case nameof(context.Item.Enable):
<EnableChip Value="context.Item.Enable">
</EnableChip>
break;
case nameof(context.Item.PluginId):
<span title=@context.Value>
@(
App.GetService<DriverPluginService>().GetNameById(context.Item.PluginId)
)
</span>
break;
default:
@if (context.Header.CellClass?.Contains("text-truncate") == true)
{
<span title=@context.Value>
@context.Value
</span>
}
else
{
@context.Value
}
break;
}
</ItemColTemplate>
</AppDataTable>
;
return renderFragment;
}
}
@code {
RenderFragment GetRenderFragment(CollectDeviceAddInput context)
{
RenderFragment renderFragment =
@<div>
<MTabs @bind-Value="tab">
<MTab Value="1" Style="height:50px;"> 基本信息 </MTab>
<MTab Value="2"> 扩展属性</MTab>
</MTabs>
<MTabsItems Value="tab">
<MTabItem Value="1">
@if (tab == 1)
{
<MCard Flat Class="ma-2">
<MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.Name)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Name />
<MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.Description)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Description />
<MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.DeviceGroup)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.DeviceGroup />
<MSubheader Class="mt-4 font-weight-black"> @(typeof(CollectDeviceRunTime).GetDescription(nameof(CollectDeviceRunTime.PluginName))) </MSubheader>
<MCascader Value="context.PluginId" Class="mt-3 mr-3" Clearable TValue=long TItem="DriverPluginCategory"
ValueChanged=@(async a=>await DriverValueChangedAsync(context,a))
Items="DriverPlugins" ItemText="u => u.Name" ItemValue="u => u.Id" ItemChildren="u => u.Children"
MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })"
ShowAllLevels="false" HideDetails="@("auto")" Height="30" Dense>
</MCascader>
<MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.Enable)) </MSubheader>
<MSwitch @bind-Value=@context.Enable />
<MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.IsLogOut)) </MSubheader>
<MSwitch @bind-Value=@context.IsLogOut />
<MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.IsRedundant)) </MSubheader>
<MSwitch @bind-Value=@context.IsRedundant />
<MSubheader Class="font-weight-black"> @context.Description(x=>x.RedundantDeviceId) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.RedundantDeviceId Outlined
Items=@(CollectDevices)
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.Name) ItemValue=@(u =>u.Id)
ItemDisabled="u => (u.PluginId!=context.PluginId||u.Id==context.Id)"
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCard>
}
</MTabItem>
<MTabItem Value="2">
@if (tab == 2)
{
<MCard Flat Class="ma-2">
<MButton Class="my-3" OnClick=@(async() =>
{
if(context.PluginId>0)
{
context.DevicePropertys= GetDriverProperties(context.PluginId,context.Id);
}
else
{
await PopupService.EnqueueSnackbarAsync("需选择驱动",AlertTypes.Error);
}
}
) Color="primary">
刷新设备属性
</MButton>
@if (context.DevicePropertys != null)
{
@foreach (var item in context.DevicePropertys)
{
<MSubheader Class="mt-4 font-weight-black"> @item.Description </MSubheader>
<MTooltip Disabled=@(item.Remark==null||item.Remark?.IsNullOrEmpty()==true) Bottom Context="tip">
<ActivatorContent>
<MTextField Type="@(item.PropertyName.Contains("Password") ? "password" : "text")" @attributes="@tip.Attrs" Dense Outlined HideDetails="@("auto")" @bind-Value=@item.Value />
</ActivatorContent>
<ChildContent>
<span>@item.Remark</span>
</ChildContent>
</MTooltip>
}
}
</MCard>
}
</MTabItem>
</MTabsItems>
</div>
;
return renderFragment;
}
}

View File

@@ -1,161 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BlazorComponent;
using Furion;
using Mapster;
using Masa.Blazor;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using SqlSugar;
using ThingsGateway.Admin.Blazor;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Admin.Core;
using ThingsGateway.Application;
namespace ThingsGateway.Blazor;
/// <summary>
/// 采集设备页面
/// </summary>
public partial class CollectDevicePage
{
readonly CollectDevicePageInput search = new();
IAppDataTable _datatable;
List<string> _deviceGroups = new();
string _searchName;
List<CollectDevice> CollectDevices = new();
List<DriverPluginCategory> DriverPlugins;
ImportExcel ImportExcel;
StringNumber tab;
[Inject]
AjaxService AjaxService { get; set; }
[CascadingParameter]
MainLayout MainLayout { get; set; }
/// <inheritdoc/>
protected override async Task OnParametersSetAsync()
{
CollectDevices = App.GetService<ICollectDeviceService>().GetCacheList();
DriverPlugins = App.GetService<IDriverPluginService>().GetDriverPluginChildrenList(DriverEnum.Collect);
_deviceGroups = CollectDevices?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList();
await base.OnParametersSetAsync();
}
private async Task AddCallAsync(CollectDeviceAddInput input)
{
await App.GetService<CollectDeviceService>().AddAsync(input);
CollectDevices = App.GetService<CollectDeviceService>().GetCacheList();
_deviceGroups = CollectDevices?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList();
await MainLayout.StateHasChangedAsync();
}
async Task CopyDevAndVarAsync(IEnumerable<CollectDevice> data)
{
if (!data.Any())
{
await PopupService.EnqueueSnackbarAsync("需选择一项或多项", AlertTypes.Warning);
return;
}
await App.GetService<CollectDeviceService>().CopyDevAndVarAsync(data);
await DatatableQueryAsync();
await PopupService.EnqueueSnackbarAsync("复制成功", AlertTypes.Success);
await MainLayout.StateHasChangedAsync();
}
async Task CopyDeviceAsync(IEnumerable<CollectDevice> data)
{
if (!data.Any())
{
await PopupService.EnqueueSnackbarAsync("需选择一项或多项", AlertTypes.Warning);
return;
}
await App.GetService<CollectDeviceService>().CopyDevAsync(data);
await DatatableQueryAsync();
await PopupService.EnqueueSnackbarAsync("复制成功", AlertTypes.Success);
await MainLayout.StateHasChangedAsync();
}
private async Task DatatableQueryAsync()
{
await _datatable?.QueryClickAsync();
}
private async Task DeleteCallAsync(IEnumerable<CollectDevice> input)
{
await App.GetService<CollectDeviceService>().DeleteAsync(input.Select(a => a.Id).ToArray());
CollectDevices = App.GetService<CollectDeviceService>().GetCacheList();
_deviceGroups = CollectDevices?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList();
await MainLayout.StateHasChangedAsync();
}
Task<Dictionary<string, ImportPreviewOutputBase>> DeviceImportAsync(IBrowserFile file)
{
return App.GetService<CollectDeviceService>().PreviewAsync(file);
}
async Task DownExportAsync(CollectDevicePageInput input = null)
{
await AjaxService.DownFileAsync("gatewayFile/collectDevice", SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat(), input.Adapt<CollectDeviceInput>());
}
private async Task DriverValueChangedAsync(CollectDeviceAddInput context, long pluginId)
{
if (pluginId <= 0) return;
if (context.DevicePropertys == null || context.DevicePropertys?.Count == 0 || context.PluginId != pluginId)
{
try
{
context.DevicePropertys = GetDriverProperties(pluginId, context.Id);
await PopupService.EnqueueSnackbarAsync("插件附加属性已更新", AlertTypes.Success);
}
catch (Exception ex)
{
await PopupService.EnqueueSnackbarAsync(ex.Message, AlertTypes.Error);
}
}
context.PluginId = pluginId;
}
private async Task EditCallAsync(CollectDeviceEditInput input)
{
await App.GetService<CollectDeviceService>().EditAsync(input);
CollectDevices = App.GetService<CollectDeviceService>().GetCacheList();
_deviceGroups = CollectDevices?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList();
await MainLayout.StateHasChangedAsync();
}
List<DependencyProperty> GetDriverProperties(long driverId, long devId)
{
return ServiceHelper.GetBackgroundService<CollectDeviceWorker>().GetDevicePropertys(driverId, devId);
}
private async Task<SqlSugarPagedList<CollectDevice>> QueryCallAsync(CollectDevicePageInput input)
{
var data = await App.GetService<CollectDeviceService>().PageAsync(input);
return data;
}
async Task SaveDeviceImportAsync(Dictionary<string, ImportPreviewOutputBase> data)
{
await App.GetService<CollectDeviceService>().ImportAsync(data);
await DatatableQueryAsync();
ImportExcel.IsShowImport = false;
await MainLayout.StateHasChangedAsync();
}
}

View File

@@ -1,161 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/gatewayconfig/config"
@using System.Linq.Expressions;
@using Masa.Blazor
@using Microsoft.AspNetCore.Authorization;
@using ThingsGateway.Admin.Application;
@using ThingsGateway.Admin.Blazor.Core;
@using ThingsGateway.Admin.Blazor;
@using ThingsGateway.Admin.Core;
@using ThingsGateway.Application;
@namespace ThingsGateway.Blazor
@attribute [Authorize]
@inject UserResoures UserResoures
@inherits BaseComponentBase
@layout MainLayout
<MCard Height=@("100%") Elevation="1" Class="pa-2">
<MTabs @bind-Value="tab">
<MTab Style="height:50px;" Value="1">
报警转储配置
</MTab>
<MTab Style="height:50px;" Value="2">
历史数据配置
</MTab>
</MTabs>
<MTabsItems Value="tab">
<MTabItem Value="1">
@if (tab == 1)
{
<MCard Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+100}px);overflow-y:auto")
Flat Class="ml-2 my-4">
<MRow NoGutters>
@foreach (var item in _alarmConfig)
{
switch (item.ConfigKey)
{
case ThingsGatewayConfigConst.Config_Alarm_ConnStr:
<MCol Class="pa-2 px-4" Md=12 Cols="12">
<MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@item.ConfigValue />
</MCol>
break;
case ThingsGatewayConfigConst.Config_Alarm_DbType:
<MCol Class="pa-2 px-4" Md=12 Cols="12">
<MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader>
<MSelect @bind-Value="item.ConfigValue" Outlined
Items=@(typeof(SqlDbType).GetEnumList()) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.des) ItemValue=@(u =>u.name)
HideDetails="true" Height="30" Dense>
</MSelect>
</MCol>
break;
case ThingsGatewayConfigConst.Config_Alarm_Enable:
<MCol Class="pa-2 px-4" Md=12 Cols="12">
<MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader>
<MSelect @bind-Value=@item.ConfigValue Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u.ToString())
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
break;
default:
<MCol Class="pa-2 px-4" Md=3 Cols="12">
<MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@item.ConfigValue />
</MCol>
break;
}
}
</MRow>
<MCardActions>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewayalarmconfig")) Class="mt-8" OnClick=OnAlarmSaveAsync Color="primary">
保存
</MButton>
</MCardActions>
</MCard>
}
</MTabItem>
<MTabItem Value="2">
@if (tab == 2)
{
<MCard Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+100}px);overflow-y:auto")
Flat Class="ml-2 my-4">
<MRow NoGutters>
@foreach (var item in _hisConfig)
{
switch (item.ConfigKey)
{
case ThingsGatewayConfigConst.Config_His_ConnStr:
<MCol Class="pa-2 px-4" Md=12 Cols="12">
<MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@item.ConfigValue />
</MCol>
break;
case ThingsGatewayConfigConst.Config_His_DbType:
<MCol Class="pa-2 px-4" Md=12 Cols="12">
<MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader>
<MSelect @bind-Value="item.ConfigValue" Outlined
Items=@(typeof(HisDbType).GetEnumList()) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.des) ItemValue=@(u =>u.name)
HideDetails="true" Height="30" Dense>
</MSelect>
</MCol>
break;
case ThingsGatewayConfigConst.Config_His_Enable:
<MCol Class="pa-2 px-4" Md=12 Cols="12">
<MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader>
<MSelect @bind-Value=@item.ConfigValue Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u.ToString())
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
break;
default:
<MCol Class="pa-2 px-4" Md=3 Cols="12">
<MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@item.ConfigValue />
</MCol>
break;
}
}
</MRow>
<MCardActions>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewayhisconfig")) Class="mt-8" OnClick=OnHisSaveAsync Color="primary">
保存
</MButton>
</MCardActions>
</MCard>
}
</MTabItem>
</MTabsItems>
</MCard>

View File

@@ -1,76 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BlazorComponent;
using Furion;
using Masa.Blazor;
using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Admin.Core;
using ThingsGateway.Application;
namespace ThingsGateway.Blazor;
/// <inheritdoc/>
public partial class ConfigPage
{
private List<SysConfig> _alarmConfig = new();
private List<SysConfig> _hisConfig = new();
StringNumber tab;
/// <inheritdoc/>
public ConfigPage()
{
AlarmHostService = ServiceHelper.GetBackgroundService<AlarmWorker>();
HistoryValueHostService = ServiceHelper.GetBackgroundService<HistoryValueWorker>();
}
AlarmWorker AlarmHostService { get; set; }
HistoryValueWorker HistoryValueHostService { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override async Task OnParametersSetAsync()
{
_alarmConfig = await App.GetService<IConfigService>().GetListByCategoryAsync(ThingsGatewayConfigConst.ThingGateway_AlarmConfig_Base);
_hisConfig = await App.GetService<IConfigService>().GetListByCategoryAsync(ThingsGatewayConfigConst.ThingGateway_HisConfig_Base);
await base.OnParametersSetAsync();
}
private async Task OnAlarmSaveAsync()
{
var confirm = await PopupService.OpenConfirmDialogAsync("确认", "保存配置后将重启报警服务,是否确定?");
if (confirm)
{
await App.GetService<ConfigService>().EditBatchAsync(_alarmConfig);
await AlarmHostService.RestartAsync();
await PopupService.EnqueueSnackbarAsync("成功", AlertTypes.Success);
}
}
private async Task OnHisSaveAsync()
{
var confirm = await PopupService.OpenConfirmDialogAsync("确认", "保存配置后将重启历史服务,是否确定?");
if (confirm)
{
await App.GetService<ConfigService>().EditBatchAsync(_hisConfig);
await HistoryValueHostService.RestartAsync();
await PopupService.EnqueueSnackbarAsync("成功", AlertTypes.Success);
}
}
}

View File

@@ -1,647 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/gatewayruntime/devicestatus"
@namespace ThingsGateway.Blazor
@using System.Linq.Expressions;
@using BlazorComponent;
@using Mapster;
@using Masa.Blazor
@using Masa.Blazor.Presets;
@using System.IO;
@using Microsoft.AspNetCore.Authorization;
@using ThingsGateway.Admin.Blazor.Core;
@using ThingsGateway.Admin.Blazor;
@using ThingsGateway.Admin.Core;
@using ThingsGateway.Application;
@using TouchSocket.Core;
@attribute [Authorize]
@inherits BaseComponentBase
@inject UserResoures UserResoures
@inject NavigationManager NavigationManager
@layout MainLayout
<MSheet Style="overflow:auto">
<MTabs @bind-Value="tab">
<MTab Style="height:50px;" Value="1">
采集设备状态
</MTab>
<MTab Style="height:50px;" Value="2">
上传设备状态
</MTab>
<MTab Style="height:50px;" Value="3">
其他服务状态
</MTab>
<MButton Class="position-button" Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicerestart")) Color="red" Dark Fab Small Loading=isAllRestart OnClick="AllRestartAsync">
<MIcon> mdi-restart </MIcon>
</MButton>
</MTabs>
<MTabsItems Value="tab">
<MTabItem Value="1">
@if (tab == 1)
{
<MRow NoGutters>
<MCol Md=2 Cols="12">
<MCard Class="ma-2" Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight + 80}px );")>
<MCardTitle>
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="mx-2 my-1" @bind-Value="_collectDeviceGroupSearchName"
Outlined Label=@typeof(CollectDevice).GetDescription(nameof(CollectDevice.DeviceGroup)) />
</MCardTitle>
<MTreeview Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight + 240}px; overflow-y:auto)") Dense TItem="string" TKey="string" ActiveChanged=@(async a=>
{
if(_collectDeviceGroup!=a.FirstOrDefault())
{
_collectDeviceGroup=a.FirstOrDefault(); CollectDeviceQuery();
}
} )
Items="_collectDeviceGroups" ItemText="r=>r" ItemChildren="r=>null"
Search="@_collectDeviceGroupSearchName"
Activatable ItemKey=@(r=>r)>
<LabelContent>
<span title=@context.Item>
@context.Item
</span>
</LabelContent>
</MTreeview>
</MCard>
</MCol>
<MCol Md=3 Cols="12">
<MCard Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight + 80}px; )") Style="overflow-y:auto;" Flat Class="ml-2 my-4">
<MVirtualScroll Context="item" Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+100}px)") OverscanCount=2 ItemSize="60" Items="_collectDeviceCores">
<ItemContent>
@if (item.Device != null)
{
<MListItem OnClick=@(()=>CollectDeviceInfo(item))>
<MListItemContent>
<MListItemTitle>
<MLabel Class=@((item.Device?.DeviceStatus==DeviceStatusEnum.OnLine?"green--text":"red--text")+$" text-h6")>
<div class="mt-1 d-flex align-center justify-space-between" title=@item.Device?.Name>
<span>@item.Device?.Name</span>
<span style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;" class="text-caption">@(item.Device?.ActiveTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " " + typeof(DeviceStatusEnum).GetDescription(item.Device?.DeviceStatus.ToString()))</span>
</div>
</MLabel>
</MListItemTitle>
</MListItemContent>
</MListItem>
<MDivider></MDivider>
}
</ItemContent>
</MVirtualScroll>
</MCard>
</MCol>
<MCol Md=7 Cols="12">
<MCard Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight + 80}px); overflow:auto)") Flat Elevation="0">
@if (collectDeviceInfoItem != null && collectDeviceInfoItem?.Device != null)
{
var item = collectDeviceInfoItem;
<MCard Style="height:100px;overflow:auto;" Flat Class="ml-4 my-4 ma-2" Elevation="0">
<MCardActions>
<div class="mr-12"></div>
<MLabel Class=@((item.Device?.DeviceStatus==DeviceStatusEnum.OnLine?"green--text":"red--text")+$" text-h6")>
<div class="mt-1 d-flex align-center justify-space-between">
<span class="mx-3">@item.Device?.Name</span>
<span style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;" class="text-caption mx-3">@(item.Device?.ActiveTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " " + typeof(DeviceStatusEnum).GetDescription(item.Device?.DeviceStatus.ToString()))</span>
</div>
</MLabel>
<MSpacer></MSpacer>
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Disabled=@(!UserResoures.IsHasPageWithRole("/gatewayruntime/devicevariable")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small Loading=isRestart
OnClick=@(()=>NavigationManager.NavigateTo("/gatewayruntime/devicevariable?devicename="+item.Device?.Name))>
<MIcon>mdi-information-outline</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>相关变量</span>
</ChildContent>
</MTooltip>
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small OnClick=@(()=>ConfigAsync(item.DeviceId,!item.Device?.KeepRun))>
<MIcon>@(item.Device?.KeepRun == true ? "mdi-pause" : "mdi-play")</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>@(item.Device?.KeepRun == true ? "暂停" : "运行")</span>
</ChildContent>
</MTooltip>
@* <MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicerestart")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small Loading=isRestart OnClick=@(()=>RestartAsync(item.DeviceId))>
<MIcon>mdi-restart</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>重启</span>
</ChildContent>
</MTooltip>*@
</MCardActions>
</MCard>
<MCard Style="height:200px;overflow:auto;" Flat Class="ml-4 my-4 ma-2" Elevation="0">
<MSubheader>
运行状态
</MSubheader>
<MRow Class="ml-2 mr-2 d-flex" NoGutters>
<MCol Md=6 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.ActiveTime)</span>
<span class="text-caption">@item.Device?.ActiveTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)</span>
</MCol>
<MCol Md=6 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.SourceVariableCount)</span>
<span class="text-caption">@item.Device?.SourceVariableCount</span>
</MCol>
<MCol Md=6 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.DeviceVariableCount)</span>
<span class="text-caption">@item.Device?.DeviceVariableCount</span>
</MCol>
<MCol Md=6 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.MethodVariableCount)</span>
<span class="text-caption">@item.Device?.MethodVariableCount</span>
</MCol>
<MCol Md=12 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.LastErrorMessage)</span>
<span class="text-caption red--text">@item.Device?.LastErrorMessage</span>
</MCol>
</MRow>
<MSubheader>
配置信息
</MSubheader>
<MRow Class="ml-2 mr-2 d-flex" NoGutters>
<MCol Md=6 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.PluginName)</span>
<span class="text-caption">@item.Device?.PluginName</span>
</MCol>
<MCol Md=6 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.IsLogOut)</span>
<span class="text-caption">@item.Device?.IsLogOut</span>
</MCol>
@foreach (var property in item.Device?.DevicePropertys ?? new())
{
<MCol Md=6 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@property.Description</span>
<span class="text-caption ">@(property.PropertyName.Contains("Password") ? "******" : @property.Value)</span>
</MCol>
}
</MRow>
</MCard>
}
<MCard Flat Class="ml-4">
@if (collectDeviceInfoItem != null && collectDeviceInfoItem?.Device != null)
{
<MCardActions>
报文日志(共享链路的日志也会相同)
<MSpacer></MSpacer>
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small
OnClick=@(()=>
{
pauseMessage=!pauseMessage;
if(pauseMessage)
CurMessages= collectDeviceInfoItem.Driver?.Messages.ToList();
}
)>
<MIcon>@((!pauseMessage) ? "mdi-pause" : "mdi-play")</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>@((!pauseMessage) ? "暂停日志" : "运行日志")</span>
</ChildContent>
</MTooltip>
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small
OnClick=@(()=>
{
if(collectDeviceInfoItem.Driver!=null)
collectDeviceInfoItem.Driver.IsSaveLog=! collectDeviceInfoItem.Driver.IsSaveLog;
}
)>
<MIcon>@((collectDeviceInfoItem.Driver?.IsSaveLog == true) ? "mdi-pause" : "mdi-play")</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>@((collectDeviceInfoItem.Driver?.IsSaveLog != true) ? "存入数据库,注意若交互频繁,可能导致数据库太大" : "不存入数据库")</span>
</ChildContent>
</MTooltip>
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Loading=isDownExport Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small
OnClick=@(async()=>
{
var curMessages= collectDeviceInfoItem.Driver?.Messages.ToList();
await DownDeviceMessageExportAsync(curMessages);
}
)>
<MIcon>mdi-export</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>导出</span>
</ChildContent>
</MTooltip>
</MCardActions>
{
ICollection<string> item = null;
if (pauseMessage)
{
item = CurMessages;
}
else if (collectDeviceInfoItem.Driver != null)
{
item = collectDeviceInfoItem.Driver?.Messages;
}
if (item == null)
{
item = new List<string>();
}
<MRow Class="ml-2 mr-2 d-flex" NoGutters>
<MVirtualScroll Context="itemMessage" TItem="string" Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+480}px)") OverscanCount=2 ItemSize="100" Items="item">
<ItemContent>
<div title=@itemMessage style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;">
@itemMessage
</div>
</ItemContent>
</MVirtualScroll>
</MRow>
}
}
</MCard>
</MCard>
</MCol>
</MRow>
}
</MTabItem>
<MTabItem Value="2">
@if (tab == 2)
{
<MRow>
<MCol Md=2 Cols="12">
<MCard Class="ma-2" Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight + 80}px); )")>
<MCardTitle>
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="mx-2 my-1" @bind-Value="_uploadDeviceGroupSearchName"
Outlined Label=@typeof(UploadDevice).GetDescription(nameof(UploadDevice.DeviceGroup)) />
</MCardTitle>
<MTreeview Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+240}px);overflow-y:auto") Dense TItem="string" TKey="string" ActiveChanged=@(async a=>
{
if(_uploadDeviceGroup!=a.FirstOrDefault())
{
_uploadDeviceGroup=a.FirstOrDefault(); UploadDeviceQuery();
}
} )
Items="_uploadDeviceGroups" ItemText="r=>r" ItemChildren="r=>null"
Search="@_uploadDeviceGroupSearchName"
Activatable ItemKey=@(r=>r)>
<LabelContent>
<span title=@context.Item>
@context.Item
</span>
</LabelContent>
</MTreeview>
</MCard>
</MCol>
<MCol Md=3 Cols="12">
<MCard Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+80}px)") Style="overflow-y:auto;" Flat Class="ml-2 my-4">
<MVirtualScroll Context="item" Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+100}px)") OverscanCount=2 ItemSize="60" Items="_uploadDeviceCores">
<ItemContent>
@if (item.Device != null)
{
<MListItem OnClick=@(()=>UploadDeviceInfo(item))>
<MListItemContent>
<MListItemTitle>
<MLabel Class=@((item.Device?.DeviceStatus==DeviceStatusEnum.OnLine?"green--text":"red--text")+$" text-h6")>
<div class="mt-1 d-flex align-center justify-space-between" title=@item.Device?.Name>
<span>@item.Device?.Name</span>
<span style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;" class="text-caption">@(item.Device?.ActiveTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " " + typeof(DeviceStatusEnum).GetDescription(item.Device?.DeviceStatus.ToString()))</span>
</div>
</MLabel>
</MListItemTitle>
</MListItemContent>
</MListItem>
<MDivider></MDivider>
}
</ItemContent>
</MVirtualScroll>
</MCard>
</MCol>
<MCol Md=7 Cols="12">
<MCard Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight + 80}px); overflow:auto)") Flat Elevation="0">
@if (uploadDeviceInfoItem != null && uploadDeviceInfoItem?.Device != null)
{
var item = uploadDeviceInfoItem;
<MCard Style="height:100px;overflow:auto;" Flat Class="ml-4 my-4 ma-2" Elevation="0">
<MCardActions>
<div class="mr-12"></div>
<MLabel Class=@((item.Device?.DeviceStatus==DeviceStatusEnum.OnLine?"green--text":"red--text")+$" text-h6")>
<div class="mt-1 d-flex align-center justify-space-between">
<span class="mx-3">@item.Device?.Name</span>
<span style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;" class="text-caption mx-3">@(item.Device?.ActiveTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " " + typeof(DeviceStatusEnum).GetDescription(item.Device?.DeviceStatus.ToString()))</span>
</div>
</MLabel>
<MSpacer></MSpacer>
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Disabled=@(!UserResoures.IsHasPageWithRole("/gatewayruntime/devicevariable")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small Loading=isRestart
OnClick=@(()=>NavigationManager.NavigateTo("/gatewayruntime/devicevariable?uploaddevicename="+item.Device?.Name))>
<MIcon>mdi-information-outline</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>相关变量</span>
</ChildContent>
</MTooltip>
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small OnClick=@(()=>UpConfigAsync(item.DeviceId,!item.Device?.KeepRun))>
<MIcon>@(item.Device?.KeepRun == true ? "mdi-pause" : "mdi-play")</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>@(item.Device?.KeepRun == true ? "暂停" : "运行")</span>
</ChildContent>
</MTooltip>
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicerestart")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small Loading=isRestart OnClick=@(()=> UpRestartAsync(item.DeviceId))>
<MIcon>mdi-restart</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>重启</span>
</ChildContent>
</MTooltip>
</MCardActions>
</MCard>
<MCard Style="height:200px;overflow:auto;" Flat Class="ml-4 my-4 ma-2" Elevation="0">
<MSubheader>
运行状态
</MSubheader>
<MRow Class="ml-2 mr-2 d-flex" NoGutters>
<MCol Md=6 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.ActiveTime)</span>
<span class="text-caption">@item.Device?.ActiveTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)</span>
</MCol>
<MCol Md=6 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.UploadVariableCount)</span>
<span class="text-caption">@item.Device?.UploadVariableCount</span>
</MCol>
<MCol Md=12 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.LastErrorMessage)</span>
<span class="text-caption red--text">@item.Device?.LastErrorMessage</span>
</MCol>
</MRow>
<MSubheader>
配置信息
</MSubheader>
<MRow Class="ml-2 mr-2 d-flex" NoGutters>
<MCol Md=6 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.PluginName)</span>
<span class="text-caption">@item.Device?.PluginName</span>
</MCol>
<MCol Md=6 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.IsLogOut)</span>
<span class="text-caption">@item.Device?.IsLogOut</span>
</MCol>
@foreach (var property in item.Device?.DevicePropertys ?? new())
{
<MCol Md=6 Cols="12" Class="px-4 mt-1 d-flex align-center justify-space-between">
<span class="text-subtitle-2 grey--text">@property.Description</span>
<span class="text-caption ">@(property.PropertyName.Contains("Password") ? "******" : @property.Value)</span>
</MCol>
}
</MRow>
</MCard>
}
<MCard Flat Class="ml-4">
@if (uploadDeviceInfoItem != null && uploadDeviceInfoItem?.Device != null)
{
<MCardActions>
报文日志(共享链路的日志也会相同)
<MSpacer></MSpacer>
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small
OnClick=@(()=>
{
pauseMessage=!pauseMessage;
if(pauseMessage)
CurMessages= uploadDeviceInfoItem.Driver?.Messages.ToList();
}
)>
<MIcon>@((!pauseMessage) ? "mdi-pause" : "mdi-play")</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>@((!pauseMessage) ? "暂停日志" : "运行日志")</span>
</ChildContent>
</MTooltip>
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small
OnClick=@(()=>
{
if(uploadDeviceInfoItem.Driver!=null)
uploadDeviceInfoItem.Driver.IsSaveLog=! uploadDeviceInfoItem.Driver.IsSaveLog;
}
)>
<MIcon>@((uploadDeviceInfoItem.Driver?.IsSaveLog == true) ? "mdi-pause" : "mdi-play")</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>@((uploadDeviceInfoItem.Driver?.IsSaveLog != true) ? "存入数据库,注意若交互频繁,可能导致数据库太大" : "不存入数据库")</span>
</ChildContent>
</MTooltip>
<MTooltip Bottom Context="tip">
<ActivatorContent>
<MButton Loading=isDownExport Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small
OnClick=@(async()=>
{
var curMessages= uploadDeviceInfoItem.Driver?.Messages.ToList();
await DownDeviceMessageExportAsync(curMessages);
}
)>
<MIcon>mdi-export</MIcon>
</MButton>
</ActivatorContent>
<ChildContent>
<span>导出</span>
</ChildContent>
</MTooltip>
</MCardActions>
{
ICollection<string> item = null;
if (pauseMessage)
{
item = CurMessages;
}
else if (uploadDeviceInfoItem.Driver != null)
{
item = uploadDeviceInfoItem.Driver?.Messages ?? new();
}
if (item == null)
{
item = new List<string>();
}
<MRow Class="ml-2 mr-2 d-flex" NoGutters>
<MVirtualScroll Context="itemMessage" TItem="string" Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+480}px)") OverscanCount=2 ItemSize="100" Items="item">
<ItemContent>
<div title=@itemMessage style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;">
@itemMessage
</div>
</ItemContent>
</MVirtualScroll>
</MRow>
}
}
</MCard>
</MCard>
</MCol>
</MRow>
}
</MTabItem>
<MTabItem Value="3">
@if (tab == 3)
{
<MRow NoGutters>
<MCard Class="ml-2 my-3" Style="width:100%" Elevation="1">
<MCardSubtitle Class=@((AlarmHostService.StatuString.IsSuccess?"green--text":"red--text")+$" text-subtitle-2")>
<div class="mt-1 d-flex align-center justify-space-between">
<span>历史报警服务状态</span>
<span class="text-caption">@AlarmHostService.StatuString.Message</span>
</div>
</MCardSubtitle>
</MCard>
</MRow>
<MRow NoGutters>
<MCard Class="ml-2 my-3" Style="width:100%" Elevation="1">
<MCardSubtitle Class=@((HistoryValueHostService.StatuString.IsSuccess?"green--text":"red--text")+$" text-subtitle-2")>
<div class="mt-1 d-flex align-center justify-space-between">
<span>历史数据服务状态</span>
<span class="text-caption">@HistoryValueHostService.StatuString.Message</span>
</div>
</MCardSubtitle>
</MCard>
</MRow>
<MRow NoGutters>
<MCard Class="ml-2 my-3" Style="width:100%" Elevation="1">
<MCardSubtitle Class=@((MemoryVariableWorker.StatuString.IsSuccess?"green--text":"red--text")+$" text-subtitle-2")>
<div class="mt-1 d-flex align-center justify-space-between">
<span>中间变量计算服务状态</span>
<span class="text-caption">@MemoryVariableWorker.StatuString.Message</span>
</div>
</MCardSubtitle>
</MCard>
</MRow>
}
</MTabItem>
</MTabsItems>
</MSheet>
<style>
.position-button {
position: fixed !important;
top: 10%;
right: 0;
box-shadow: 1px 1px 8px var(--mud-palette-primary);
background-color: var(--mud-palette-primary);
cursor: pointer
}
</style>

View File

@@ -1,274 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BlazorComponent;
using Mapster;
using Masa.Blazor;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using SqlSugar;
using System.IO;
using System.Threading;
using ThingsGateway.Admin.Blazor;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Admin.Core;
using ThingsGateway.Application;
namespace ThingsGateway.Blazor;
/// <summary>
/// 设备状态页面
/// </summary>
public partial class DeviceStatusPage : IDisposable
{
readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(1));
List<CollectDeviceCore> _collectDeviceCores = new();
private string _collectDeviceGroup;
List<string> _collectDeviceGroups = new();
string _collectDeviceGroupSearchName;
List<UploadDeviceCore> _uploadDeviceCores = new();
private string _uploadDeviceGroup;
List<string> _uploadDeviceGroups = new();
string _uploadDeviceGroupSearchName;
CollectDeviceCore collectDeviceInfoItem;
List<string> CurMessages = new();
bool isAllRestart;
private bool isDownExport;
bool isRestart;
bool pauseMessage;
StringNumber tab;
UploadDeviceCore uploadDeviceInfoItem;
AlarmWorker AlarmHostService { get; set; }
CollectDeviceWorker CollectDeviceHostService { get; set; }
[Inject]
GlobalDeviceData GlobalDeviceData { get; set; }
IJSObjectReference Helper { get; set; }
HistoryValueWorker HistoryValueHostService { get; set; }
[Inject]
InitTimezone InitTimezone { get; set; }
[Inject]
IJSRuntime JS { get; set; }
[CascadingParameter]
MainLayout MainLayout { get; set; }
MemoryVariableWorker MemoryVariableWorker { get; set; }
StringNumber Panel { get; set; }
UploadDeviceWorker UploadDeviceHostService { get; set; }
StringNumber Uppanel { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
public override void Dispose()
{
_periodicTimer?.Dispose();
base.Dispose();
}
/// <inheritdoc/>
protected override void OnInitialized()
{
CollectDeviceHostService = ServiceHelper.GetBackgroundService<CollectDeviceWorker>();
UploadDeviceHostService = ServiceHelper.GetBackgroundService<UploadDeviceWorker>();
AlarmHostService = ServiceHelper.GetBackgroundService<AlarmWorker>();
HistoryValueHostService = ServiceHelper.GetBackgroundService<HistoryValueWorker>();
MemoryVariableWorker = ServiceHelper.GetBackgroundService<MemoryVariableWorker>();
_ = RunTimerAsync();
base.OnInitialized();
}
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnParametersSet()
{
CollectDeviceQuery();
UploadDeviceQuery();
base.OnParametersSet();
}
async Task AllRestartAsync()
{
try
{
var confirm = await PopupService.OpenConfirmDialogAsync("重启", "确定重启?");
if (confirm)
{
isAllRestart = true;
StateHasChanged();
PopupService.ShowProgressLinear();
await Task.Run(async () => await CollectDeviceHostService.RestartDeviceThreadAsync());
CollectDeviceQuery();
UploadDeviceQuery();
}
}
finally
{
collectDeviceInfoItem = null;
uploadDeviceInfoItem = null;
isAllRestart = false;
PopupService.HideProgressLinear();
await MainLayout.StateHasChangedAsync();
}
}
void CollectDeviceInfo(CollectDeviceCore item)
{
collectDeviceInfoItem = item;
CurMessages = new();
}
void CollectDeviceQuery()
{
_collectDeviceGroups = GlobalDeviceData.CollectDevices.Adapt<List<CollectDevice>>()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList() ?? new();
_collectDeviceCores = CollectDeviceHostService?.CollectDeviceCores?.WhereIF(!_collectDeviceGroup.IsNullOrEmpty(), a => a.Device?.DeviceGroup == _collectDeviceGroup).ToList() ?? new();
}
async Task ConfigAsync(long devId, bool? isStart)
{
var str = isStart == true ? "启动" : "暂停";
var confirm = await PopupService.OpenConfirmDialogAsync(str, $"确定{str}?");
if (confirm)
{
CollectDeviceHostService.ConfigDeviceThread(devId, isStart == true);
}
}
async Task DownDeviceMessageExportAsync(List<string> values)
{
try
{
isDownExport = true;
StateHasChanged();
using var memoryStream = new MemoryStream();
using StreamWriter writer = new(memoryStream);
foreach (var item in values)
{
writer.WriteLine(item);
}
writer.Flush();
memoryStream.Seek(0, SeekOrigin.Begin);
using var streamRef = new DotNetStreamReference(stream: memoryStream);
Helper ??= await JS.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Admin.Blazor.Core/js/downloadFileFromStream.js");
await Helper.InvokeVoidAsync("downloadFileFromStream", $"报文导出{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.txt", streamRef);
}
finally
{
isDownExport = false;
}
}
//去除单个采集重启
//async Task RestartAsync(long devId)
//{
// try
// {
// var confirm = await PopupService.OpenConfirmDialogAsync("重启", "确定重启?");
// if (confirm)
// {
// isRestart = true;
// StateHasChanged();
// await Task.Run(async () => await CollectDeviceHostService.UpDeviceThreadAsync(devId));
// collectDeviceInfoItem = null;
// CollectDeviceQuery();
// }
// }
// catch (Exception ex)
// {
// await PopupService.EnqueueSnackbarAsync(ex.Message, AlertTypes.Warning);
// }
// finally
// {
// isRestart = false;
// await MainLayout.StateHasChangedAsync();
// }
//}
private async Task RunTimerAsync()
{
while (await _periodicTimer.WaitForNextTickAsync())
{
try
{
{
_collectDeviceGroups = GlobalDeviceData.CollectDevices.Adapt<List<CollectDevice>>()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList() ?? new();
_collectDeviceCores = CollectDeviceHostService?.CollectDeviceCores?.WhereIF(!_collectDeviceGroup.IsNullOrEmpty(), a => a.Device?.DeviceGroup == _collectDeviceGroup).ToList() ?? new();
}
if (_collectDeviceCores?.FirstOrDefault()?.Device == null || CollectDeviceHostService?.CollectDeviceCores.Count != _collectDeviceCores.Count)
{
CollectDeviceQuery();
}
if (_uploadDeviceCores?.FirstOrDefault()?.Device == null || UploadDeviceHostService?.UploadDeviceCores.Count != _uploadDeviceCores.Count)
{
UploadDeviceQuery();
}
await InvokeAsync(StateHasChanged);
}
catch
{
}
}
}
async Task UpConfigAsync(long devId, bool? isStart)
{
var str = isStart == true ? "启动" : "暂停";
var confirm = await PopupService.OpenConfirmDialogAsync(str, $"确定{str}?");
if (confirm)
{
UploadDeviceHostService.ConfigDeviceThread(devId, isStart == true);
}
}
void UploadDeviceInfo(UploadDeviceCore item)
{
uploadDeviceInfoItem = item;
}
void UploadDeviceQuery()
{
_uploadDeviceGroups = UploadDeviceHostService.UploadDeviceCores.Select(a => a.Device)?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList() ?? new();
_uploadDeviceCores = UploadDeviceHostService?.UploadDeviceCores?.WhereIF(!_uploadDeviceGroup.IsNullOrEmpty(), a => a.Device?.DeviceGroup == _uploadDeviceGroup).ToList() ?? new();
}
async Task UpRestartAsync(long devId)
{
try
{
var confirm = await PopupService.OpenConfirmDialogAsync("重启", "确定重启?");
if (confirm)
{
isRestart = true;
StateHasChanged();
await Task.Run(async () => await UploadDeviceHostService.UpDeviceThreadAsync(devId));
uploadDeviceInfoItem = null;
UploadDeviceQuery();
}
}
catch (Exception ex)
{
await PopupService.EnqueueSnackbarAsync(ex.Message, AlertTypes.Warning);
}
finally
{
isRestart = false;
await MainLayout.StateHasChangedAsync();
}
}
}

View File

@@ -1,663 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/gatewayconfig/devicevariable"
@namespace ThingsGateway.Blazor
@using System.Linq.Expressions;
@using BlazorComponent;
@using Furion.DataValidation;
@using Furion;
@using Mapster;
@using Masa.Blazor
@using Masa.Blazor.Presets;
@using System.IO;
@using Microsoft.AspNetCore.Authorization;
@using ThingsGateway.Admin.Blazor.Core;
@using ThingsGateway.Admin.Blazor;
@using ThingsGateway.Admin.Core;
@using ThingsGateway.Application;
@attribute [Authorize]
@inherits BaseComponentBase
@inject UserResoures UserResoures
@layout MainLayout
@if (IsMobile)
{
@GetAppDataTable()
}
else
{
<MRow>
<MCol Md=2 Cols="12">
<MCard Class="ma-2" Height=@("100%")>
<MCardTitle>
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="mx-2 my-1" @bind-Value="_searchName"
Outlined Label=@typeof(CollectDevice).GetDescription(nameof(CollectDevice.DeviceGroup)) />
</MCardTitle>
<MTreeview Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+100}px);overflow-y:auto") Dense TItem="DeviceTree" TKey="string" OpenOnClick ActiveChanged=@(async a=>
{
if(search.DeviceName!=a.FirstOrDefault())
{
search.DeviceName=a.FirstOrDefault();
await DatatableQueryAsync();
}
} )
Items="_deviceGroups" ItemText="r=>r.Name" ItemChildren="r=>r.Childrens"
Search="@_searchName"
Activatable ItemKey=@(r=>r.Name)>
<LabelContent>
<span title=@context.Item.Name>
@context.Item.Name
</span>
</LabelContent>
</MTreeview>
</MCard>
</MCol>
<MCol Md=10 Cols="12">
@GetAppDataTable()
</MCol>
</MRow>
}
<ImportExcel @ref=ImportExcel Import="SaveDeviceImportAsync" Preview="DeviceImportAsync" />
@code {
RenderFragment GetAppDataTable()
{
RenderFragment renderFragment =
@<AppDataTable @ref="_datatable"
StyleString=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+10}px)")
TItem="DeviceVariable" SearchItem="DeviceVariablePageInput"
AddItem="DeviceVariableAddInput" EditItem="VariableEditInput"
IsMenuOperTemplate=false SearchModel="search"
QueryCallAsync="QueryCallAsync" AddCallAsync="AddCallAsync"
EditCallAsync="EditCallAsync" DeleteCallAsync="DeleteCallAsync"
IsShowDetailButton
IsShowQueryButton
IsShowAddButton=@UserResoures.IsHasButtonWithRole("gatewayvariableadd")
IsShowDeleteButton=@UserResoures.IsHasButtonWithRole("gatewayvariabledelete")
IsShowEditButton=@UserResoures.IsHasButtonWithRole("gatewayvariableedit")>
<SearchTemplate>
<MTextField Dense
Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.Name"
Clearable
Outlined
Label=@context.Description(x => x.Name) />
<MTextField Dense
Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.VariableAddress"
Clearable
Outlined
Label=@context.Description(x => x.VariableAddress) />
<MTextField Dense
Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.DeviceName"
Clearable
Outlined
Label=@context.Description(x => x.DeviceName) />
<MTextField Dense
Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.UploadDeviceName"
Clearable
Outlined
Label=@context.Description(x => x.UploadDeviceName) />
</SearchTemplate>
<OtherToolbarTemplate>
<MMenu OffsetY
Context="menu">
<ActivatorContent>
<MButton @attributes="@menu.Attrs" Color="primary"
Class="my-1 mx-2 ">
导出
<AppChevronDown></AppChevronDown>
</MButton>
</ActivatorContent>
<ChildContent>
<MList>
<MListItem OnClick="()=>DownExportAsync()"> 导出全部 </MListItem>
<MListItem OnClick="()=>DownExportAsync(search)"> 导出搜索项 </MListItem>
</MList>
</ChildContent>
</MMenu>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewayvariableedit")) Class="my-1 mx-2" OnClick="()=>{ ImportExcel.Step=1; ImportExcel.IsShowImport=true;}" Color="primary">
导入
</MButton>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewayvariableedit")) Class="my-1 mx-2" OnClick=ClearAsync Color="primary">
清空
</MButton>
</OtherToolbarTemplate>
<AddTemplate>
@{
var data = CollectDevices.FirstOrDefault();
context.DeviceId = context.DeviceId == 0 ? data == null ? 0 : data.Id : context.DeviceId;
}
@GetRenderFragment(context)
</AddTemplate>
<EditTemplate>
@GetRenderFragment(context)
</EditTemplate>
<ItemColTemplate>
@switch (context.Header.Value)
{
case nameof(context.Item.DeviceId):
<span title=@context.Value>
@(
App.GetService<ICollectDeviceService>().GetNameById(context.Item.DeviceId)
)
</span>
break;
default:
@if (context.Header.CellClass?.Contains("text-truncate") == true)
{
<span title=@context.Value>
@context.Value
</span>
}
else
{
@context.Value
}
break;
}
</ItemColTemplate>
<Detailemplate>
@{
switch (context.Item1.Value)
{
case nameof(DeviceVariable.DeviceId):
<tr @key="context.Item1.Text">
<td class="text-start table-minwidth">
@context.Item1.Text
</td>
<td class="text-start ">
<div style="word-break:break-all; white-space:pre-wrap;">
@(App.GetService<ICollectDeviceService>().GetNameById(context.Item2.ToLong()))
</div>
</td>
</tr>
break;
default:
<tr @key="context.Item1.Text">
<td class="text-start table-minwidth">
@context.Item1.Text
</td>
<td class="text-start ">
<div style="word-break:break-all; white-space:pre-wrap;">
@context.Item2
</div>
</td>
</tr>
break;
}
}
</Detailemplate>
</AppDataTable>
;
return renderFragment;
}
RenderFragment GetRenderFragment(VariableEditInput context)
{
if (!OtherMethods.ContainsKey(context.DeviceId))
{
DeviceChanged(context.DeviceId);
}
RenderFragment renderFragment =
@<div>
<MTabs @bind-Value="tab">
<MTab Value=1 Style="height:50px;"> 基本属性 </MTab>
<MTab Value=2 Style="height:50px;"> 报警属性 </MTab>
<MTab Value=3 Style="height:50px;"> 历史属性 </MTab>
<MTab Value=4 Style="height:50px;"> 上传属性 </MTab>
</MTabs>
<MTabsItems Value="tab">
<MTabItem Value="1">
@if (tab == 1)
{
<MCard Flat Class="ma-2">
<MRow NoGutters Class="my-2" Justify="JustifyTypes.Start">
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.Name)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Name />
</MCol>
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.Description)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Description />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @context.Description(x=>x.DeviceId) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.DeviceId Outlined
Items=@(CollectDevices) OnClick=@(()=>DeviceChanged(context.DeviceId))
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.Name) ItemValue=@(u =>u.Id)
ItemDisabled="u => !u.Enable"
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.IntervalTime)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.IntervalTime />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.RpcWriteEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.RpcWriteEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.Unit)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Unit />
</MCol>
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.VariableAddress)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.VariableAddress />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.ProtectTypeEnum)) </MSubheader>
<MSelect Class="mr-3" @bind-Value="context.ProtectTypeEnum" Outlined
Items=@(typeof(ProtectTypeEnum).GetEnumList())
MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })"
ItemText=@((u) =>u.des)
ItemValue=@(u =>(ProtectTypeEnum)u.value)
HideDetails=@("auto") Height="30"
Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.DataTypeEnum)) </MSubheader>
<MSelect Class="mr-3" @bind-Value="context.DataTypeEnum" Outlined
Items=@(typeof(DataTypeEnum).GetEnumList()) Clearable
MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })"
ItemText=@((u) =>u.des)
ItemValue=@(u =>(DataTypeEnum)u.value)
HideDetails=@("auto") Height="30"
Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.ReadExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.ReadExpressions />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.WriteExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.WriteExpressions />
</MCol>
@if (OtherMethods.ContainsKey(context.DeviceId))
{
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.OtherMethod)) </MSubheader>
<MSelect Class="mr-3" @bind-Value="context.OtherMethod" Outlined
Items=@(OtherMethods[context.DeviceId]) Clearable
MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })"
ItemText=@((u) =>u)
ItemValue=@(u =>u)
HideDetails=@("auto") Height="30"
Dense>
</MSelect>
</MCol>
}
</MRow>
</MCard>
}
</MTabItem>
<MTabItem Value="2">
@if (tab == 2)
{
<MCard Flat Class="ma-2">
<MRow NoGutters Class="my-2" Justify="JustifyTypes.Center">
<MCol Md=12 Cols=12 class="px-1 py-2">
<MDivider Center Height="2"></MDivider>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolCloseAlarmEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.BoolCloseAlarmEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolCloseAlarmText)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.BoolCloseAlarmText />
</MCol>
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolCloseRestrainExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.BoolCloseRestrainExpressions />
</MCol>
<MCol Md=12 Cols=12 class="px-1 py-2">
<MDivider Center Height="2"></MDivider>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolOpenAlarmEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.BoolOpenAlarmEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolOpenAlarmText)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.BoolOpenAlarmText />
</MCol>
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolOpenRestrainExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.BoolOpenRestrainExpressions />
</MCol>
<MCol Md=12 Cols=12 class="px-1 py-2">
<MDivider Center Height="2"></MDivider>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HHAlarmEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.HHAlarmEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HHAlarmText)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HHAlarmText />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HHAlarmCode)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HHAlarmCode />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HHRestrainExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HHRestrainExpressions />
</MCol>
<MCol Md=12 Cols=12 class="px-1 py-2">
<MDivider Center Height="2"></MDivider>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HAlarmEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.HAlarmEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HAlarmText)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HAlarmText />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HAlarmCode)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HAlarmCode />
</MCol>
<MCol Md=6 Cols=12 class="px-1 py-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HRestrainExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HRestrainExpressions />
</MCol>
<MCol Md=12 Cols=12 class="px-1 py-2">
<MDivider Center Height="2"></MDivider>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LAlarmEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.LAlarmEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LAlarmText)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LAlarmText />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LAlarmCode)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LAlarmCode />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LRestrainExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LRestrainExpressions />
</MCol>
<MCol Md=12 Cols=12 class="px-1 py-2">
<MDivider Center Height="2"></MDivider>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LLAlarmEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.LLAlarmEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LLAlarmText)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LLAlarmText />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LLAlarmCode)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LLAlarmCode />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LLRestrainExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LLRestrainExpressions />
</MCol>
</MRow>
</MCard>
}
</MTabItem>
<MTabItem Value="3">
@if (tab == 3)
{
<MCard Flat Class="ma-2">
<MRow NoGutters Class="my-2" Justify="JustifyTypes.Center">
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HisEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.HisEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HisType)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.HisType Outlined
Items=@(typeof(HisType).GetEnumList()) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.des)
ItemValue=@(u =>(HisType)u.value)
HideDetails=@("auto") Height="30"
Dense>
</MSelect>
</MCol>
</MRow>
</MCard>
}
</MTabItem>
<MTabItem Value="4">
@if (tab == 4)
{
<MCard Flat Class="pa-2">
<MRow Class="px-1 py-6" Align="AlignTypes.Center">
<MSelect Class="mr-3" @bind-Value=@choiceUploadDeviceId
Outlined
Items=@(UploadDevices)
Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.Name) ItemValue=@(u =>u.Id)
HideDetails=@("auto") Height="30"
Dense>
</MSelect>
<MButton Class="my-3" OnClick=@(async() =>
{
if(choiceUploadDeviceId>0)
{
var data=GetDriverProperties(UploadDevices.FirstOrDefault(a=>a.Id==choiceUploadDeviceId).PluginId,context.VariablePropertys.ContainsKey(choiceUploadDeviceId)?context.VariablePropertys[choiceUploadDeviceId]:null);
if(data?.Count>0)
{
context.VariablePropertys.AddOrUpdate(choiceUploadDeviceId,a=> data,(a,b)=>data);
}
else
{
await PopupService.EnqueueSnackbarAsync("此上传设备没有变量上传属性",AlertTypes.Warning);
context.VariablePropertys.Remove(choiceUploadDeviceId);
}
}
else
{
await PopupService.EnqueueSnackbarAsync("需选择上传设备",AlertTypes.Warning);
}
}
) Color="primary">
添加/刷新属性
</MButton>
</MRow>
@if (context.VariablePropertys != null)
{
@foreach (var item in context.VariablePropertys)
{
<MCard Class="pa-2 my-3">
<MSubheader Class="mt-4 font-weight-black">
@(
UploadDevices.FirstOrDefault(a => a.Id == item.Key)?.Name ?? "未知"
)
</MSubheader>
@foreach (var property in item.Value ?? new())
{
<MSubheader Class="mt-4 font-weight-black"> @property.Description </MSubheader>
<MTooltip Disabled=string.IsNullOrEmpty(property.Remark)
Bottom
Context="tip">
<ActivatorContent>
<MTextField @attributes="@tip.Attrs"
Dense
Outlined
HideDetails="@("auto")" @bind-Value=@property.Value />
</ActivatorContent>
<ChildContent>
<span>@property.Remark</span>
</ChildContent>
</MTooltip>
}
<MCardActions>
<MButton Class="my-3" OnClick=@(() =>
{
context.VariablePropertys.Remove(item.Key);
}
) Color="primary">
删除
</MButton>
</MCardActions>
</MCard>
}
}
</MCard>
}
</MTabItem>
</MTabsItems>
</div>
;
return renderFragment;
}
}

View File

@@ -1,67 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BlazorComponent;
using Furion;
using Masa.Blazor;
using Microsoft.AspNetCore.Components;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Application;
namespace ThingsGateway.Blazor;
/// <summary>
/// 调试页面
/// </summary>
public partial class DriverDebugPage
{
readonly List<DeviceTree> _deviceGroups = new();
private BootstrapDynamicComponent _importComponent;
private object _importRef;
private RenderFragment _importRender;
string _searchName;
List<DriverPluginCategory> DriverPlugins;
bool IsShowTreeView = true;
PluginDebugUIInput SearchModel { get; set; } = new();
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
DriverPlugins = App.GetService<DriverPluginService>().GetDriverPluginChildrenList();
base.OnInitialized();
}
/// <inheritdoc/>
async Task ImportVaiableAsync(long driverId)
{
var driver = ServiceHelper.GetBackgroundService<CollectDeviceWorker>().GetDebugUI(driverId);
if (driver == null)
{
await PopupService.EnqueueSnackbarAsync("插件未实现调试页面", AlertTypes.Warning);
return;
}
_importComponent = new BootstrapDynamicComponent(driver);
_importRender = _importComponent.Render(a => _importRef = a);
}
class PluginDebugUIInput
{
public long PluginId { get; set; }
public string PluginName { get; set; }
}
}

View File

@@ -1,99 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/gatewayruntime/hisalarm"
@namespace ThingsGateway.Blazor
@using System.Linq.Expressions;
@using BlazorComponent;
@using Furion.DataValidation;
@using Mapster;
@using Masa.Blazor.Presets;
@using System.IO;
@using Masa.Blazor;
@using Microsoft.AspNetCore.Authorization;
@using ThingsGateway.Admin.Blazor.Core;
@using ThingsGateway.Admin.Blazor;
@using ThingsGateway.Admin.Core;
@using ThingsGateway.Application;
@using TouchSocket.Core;
@attribute [Authorize]
@inherits BaseComponentBase
@inject UserResoures UserResoures
@layout MainLayout
<AppDataTable @ref="_datatable" TItem="HistoryAlarm" SearchItem="HisPageInput"
AddItem="object" EditItem="object" IsShowSelect=false
IsMenuOperTemplate=false SearchModel="SearchModel"
QueryCallAsync="QueryCallAsync"
IsShowDetailButton
IsShowQueryButton>
<SearchTemplate>
<MMenu CloseOnContentClick="false" OffsetY Context="menu">
<ActivatorContent>
<MTextField Dense Readonly Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 "
Value="context.StartTime.Value.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)"
@attributes="menu.Attrs" Outlined Label=@context.Description(x => x.StartTime) />
</ActivatorContent>
<ChildContent>
<AppDateTimePicker @bind-Value="context.StartTime"></AppDateTimePicker>
</ChildContent>
</MMenu>
<MMenu CloseOnContentClick="false" OffsetY Context="menu">
<ActivatorContent>
<MTextField Dense Readonly Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 "
Value="context.EndTime.Value.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)" Clearable
@attributes="menu.Attrs" Outlined Label=@context.Description(x => x.EndTime) />
</ActivatorContent>
<ChildContent>
<AppDateTimePicker @bind-Value="context.EndTime"></AppDateTimePicker>
</ChildContent>
</MMenu>
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.Name" Clearable
Outlined Label=@context.Description(x => x.Name) />
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.DeviceName" Clearable
Outlined Label=@context.Description(x => x.DeviceName) />
</SearchTemplate>
<ItemColTemplate>
@switch (context.Header.Value)
{
case nameof(context.Item.IsOnline):
<EnableChip Value="context.Item.IsOnline" DisabledLabel="离线" EnabledLabel="在线">
</EnableChip>
break;
default:
@if (context.Header.CellClass?.Contains("text-truncate") == true)
{
<span title=@context.Value>
@context.Value
</span>
}
else
{
@context.Value
}
break;
}
</ItemColTemplate>
</AppDataTable>

View File

@@ -1,103 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BlazorComponent;
using Masa.Blazor;
using Microsoft.AspNetCore.Components;
using SqlSugar;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Admin.Core;
using ThingsGateway.Application;
namespace ThingsGateway.Blazor;
/// <summary>
/// 历史报警页面
/// </summary>
public partial class HistoryAlarmPage
{
private IAppDataTable _datatable;
AlarmWorker AlarmHostService { get; set; }
[Inject]
InitTimezone InitTimezone { get; set; }
HisPageInput SearchModel { get; set; } = new();
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{
AlarmHostService = ServiceHelper.GetBackgroundService<AlarmWorker>();
await base.OnInitializedAsync();
}
private async Task DatatableQuery()
{
await _datatable?.QueryClickAsync();
}
private async Task<SqlSugarPagedList<HistoryAlarm>> QueryCallAsync(HisPageInput input)
{
var result = await AlarmHostService.GetAlarmDbAsync();
if (result.IsSuccess)
{
return await Task.Run(async () =>
{
try
{
var query = result.Content.CopyNew().Queryable<HistoryAlarm>().
WhereIF(!input.DeviceName.IsNullOrEmpty(), a => a.DeviceName.Contains(input.DeviceName))
.WhereIF(!input.Name.IsNullOrEmpty(), a => a.Name.Contains(input.Name))
.WhereIF(input.StartTime != null, a => a.EventTime >= input.StartTime.Value.ToLocalTime())
.WhereIF(input.EndTime != null, a => a.EventTime <= input.EndTime.Value.ToLocalTime());
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
var data = await query.ToPagedListAsync(input.Current, input.Size);
return data;
}
catch (Exception ex)
{
await InvokeAsync(async () => await PopupService.EnqueueSnackbarAsync("查询失败,请检查网络连接:" + ex.Message, AlertTypes.Warning));
return new()
{
Current = 1,
Size = 10,
Pages = 0,
Records = new List<HistoryAlarm>(),
Total = 0
};
}
});
}
else
{
await InvokeAsync(async () => await PopupService.EnqueueSnackbarAsync(result.Message, AlertTypes.Warning));
return new()
{
Current = 1,
Size = 10,
Pages = 0,
Records = new List<HistoryAlarm>(),
Total = 0
};
}
}
}

View File

@@ -1,108 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/gatewayruntime/historyvalue"
@namespace ThingsGateway.Blazor
@using System.Linq.Expressions;
@using BlazorComponent;
@using Furion.DataValidation;
@using Mapster;
@using Masa.Blazor.Presets;
@using System.IO;
@using Masa.Blazor;
@using Microsoft.AspNetCore.Authorization;
@using ThingsGateway.Admin.Blazor.Core;
@using ThingsGateway.Admin.Blazor;
@using ThingsGateway.Admin.Core;
@using ThingsGateway.Application;
@using TouchSocket.Core;
@attribute [Authorize]
@inject MasaBlazor MasaBlazor
@inherits BaseComponentBase
@inject UserResoures UserResoures
@layout MainLayout
@inject InitTimezone InitTimezone
<AppDataTable @ref="_datatable" TItem="HistoryValue" SearchItem="HisPageInput"
AddItem="object" EditItem="object" IsShowSelect=false
IsMenuOperTemplate=false
QueryCallAsync="QueryCallAsync"
IsShowDetailButton
IsShowQueryButton>
<SearchTemplate>
<MMenu CloseOnContentClick="false" OffsetY Context="menu">
<ActivatorContent>
<MTextField Dense Readonly Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 "
Value="context.StartTime.Value.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)"
Clearable
@attributes="menu.Attrs" Outlined Label=@context.Description(x => x.StartTime) />
</ActivatorContent>
<ChildContent>
<AppDateTimePicker @bind-Value="context.StartTime"></AppDateTimePicker>
</ChildContent>
</MMenu>
<MMenu CloseOnContentClick="false" OffsetY Context="menu">
<ActivatorContent>
<MTextField Dense Readonly Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 "
Value="context.EndTime.Value.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)" Clearable
@attributes="menu.Attrs" Outlined Label=@context.Description(x => x.EndTime) />
</ActivatorContent>
<ChildContent>
<AppDateTimePicker @bind-Value="context.EndTime"></AppDateTimePicker>
</ChildContent>
</MMenu>
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.Name" Clearable
Outlined Label=@context.Description(x => x.Name) />
</SearchTemplate>
<ItemColWithDTTemplate>
@if (context?.Value?.GetType() == typeof(DateTime))
{
<span>
@((DateTime.SpecifyKind((DateTime)context.Value, DateTimeKind.Utc)).ToLocalTime().ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset))
</span>
}
else
{
@switch (context.Header.Value)
{
case nameof(context.Item.IsOnline):
<EnableChip Value="context.Item.IsOnline" DisabledLabel="离线" EnabledLabel="在线">
</EnableChip>
break;
default:
@if (context.Header.CellClass?.Contains("text-truncate") == true)
{
<span title=@context.Value>
@context.Value
</span>
}
else
{
@context.Value
}
break;
}
}
</ItemColWithDTTemplate>
</AppDataTable>

View File

@@ -1,101 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BlazorComponent;
using Masa.Blazor;
using SqlSugar;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Admin.Core;
using ThingsGateway.Application;
namespace ThingsGateway.Blazor;
/// <summary>
/// 时序库页面
/// </summary>
public partial class HistoryValuePage
{
HisPageInput SearchModel { get; set; } = new();
private IAppDataTable _datatable;
HistoryValueWorker HistoryValueHostService { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{
HistoryValueHostService = ServiceHelper.GetBackgroundService<HistoryValueWorker>();
await base.OnInitializedAsync();
}
private async Task DatatableQuery()
{
await _datatable?.QueryClickAsync();
}
private async Task<SqlSugarPagedList<HistoryValue>> QueryCallAsync(HisPageInput input)
{
var result = await HistoryValueHostService.GetHisDbAsync();
if (result.IsSuccess)
{
try
{
return await Task.Run(async () =>
{
var query = result.Content.CopyNew().Queryable<HistoryValue>()
.WhereIF(!input.Name.IsNullOrEmpty(), a => a.Name.Contains(input.Name))
.WhereIF(input.StartTime != null, a => a.CollectTime >= input.StartTime.Value)
.WhereIF(input.EndTime != null, a => a.CollectTime <= input.EndTime.Value);
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
var data = await query.ToPagedListAsync(input.Current, input.Size);
return data;
});
}
catch (Exception ex)
{
await InvokeAsync(async () => await PopupService.EnqueueSnackbarAsync("查询失败,请检查网络连接:" + ex.Message, AlertTypes.Warning));
return new()
{
Current = 1,
Size = 10,
Pages = 0,
Records = new List<HistoryValue>(),
Total = 0
};
}
}
else
{
await InvokeAsync(async () => await PopupService.EnqueueSnackbarAsync(result.Message, AlertTypes.Warning));
return new()
{
Current = 1,
Size = 10,
Pages = 0,
Records = new List<HistoryValue>(),
Total = 0
};
}
}
}

View File

@@ -1,198 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/gatewayconfig/manage"
@namespace ThingsGateway.Blazor
@using System.Linq.Expressions;
@using BlazorComponent;
@using MQTTnet.Server;
@using Mapster;
@using Masa.Blazor.Presets;
@using System.IO;
@using Masa.Blazor;
@using Microsoft.AspNetCore.Authorization;
@using ThingsGateway.Admin.Blazor.Core;
@using ThingsGateway.Admin.Blazor;
@using ThingsGateway.Admin.Core;
@using ThingsGateway.Application;
@using TouchSocket.Core;
@attribute [Authorize]
@inject MasaBlazor MasaBlazor
@inherits BaseComponentBase
@inject UserResoures UserResoures
@inject NavigationManager NavigationManager
@layout MainLayout
<MRow NoGutters>
<MExpansionPanels @bind-Values="Panel"
Multiple>
<MExpansionPanel Value="1">
<MExpansionPanelHeader>
@($"子网关服务信息-{ManageGatewayWorker.ClientStatuString.Message}")
</MExpansionPanelHeader>
<MExpansionPanelContent>
@{
var config = ManageGatewayWorker.ClientGatewayConfig;
}
@if (config != null)
{
<MDescriptions Title="子网关服务配置信息" Bordered="true">
<MDescriptionsItem Label=@config.Description(a=>a.Enable)>@config.Enable</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.GatewayId)>@config.GatewayId</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.MqttBrokerIP)>@config.MqttBrokerIP</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.MqttBrokerPort)>@config.MqttBrokerPort</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.UserName)>@config.UserName</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.Password)>@config.Password</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.DBDownTopic)>@config.DBDownTopic</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.DBUploadTopic)>@config.DBUploadTopic</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.WriteRpcTopic)>@config.WriteRpcTopic</MDescriptionsItem>
</MDescriptions>
}
</MExpansionPanelContent>
</MExpansionPanel>
<MExpansionPanel Value="2">
<MExpansionPanelHeader>
@($"管理服务信息-{ManageGatewayWorker.ManageStatuString.Message}")
</MExpansionPanelHeader>
<MExpansionPanelContent>
@{
var config = ManageGatewayWorker.ManageGatewayConfig;
}
@if (config != null)
{
<MDescriptions Title="管理服务配置信息" Bordered="true">
<MDescriptionsItem Label=@config.Description(a=>a.Enable)>@config.Enable</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.MqttBrokerIP)>@config.MqttBrokerIP</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.MqttBrokerPort)>@config.MqttBrokerPort</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.UserName)>@config.UserName</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.Password)>@config.Password</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.DBDownTopic)>@config.DBDownTopic</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.DBUploadTopic)>@config.DBUploadTopic</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.WriteRpcTopic)>@config.WriteRpcTopic</MDescriptionsItem>
</MDescriptions>
}
<MCard Flat Class="ma-0" Style="min-height:1000px">
<div class="m-descriptions-header__title my-2">
当前服务下的子网关
</div>
<MRow NoGutters>
<MCol Md=3>
<MTreeview Dense TItem="MqttClientStatus"
TKey="MqttClientStatus" OpenOnClick @bind-Active=CurClients
Items="MqttClientStatuses" ItemText=@(r=>r.Id) ItemChildren="r=> null"
Activatable ItemKey=@(r=>r)>
<LabelContent>
<span title=@context.Item.Id>
@(context.Item.Id + "-" + context.Item.Endpoint)
</span>
</LabelContent>
</MTreeview>
</MCol>
<MCol Md=9>
@if (CurClients != null && CurClients.Count > 0)
{
var CurClient = CurClients.FirstOrDefault();
<MCard Flat Class="ml-4">
<MDescriptions Title="当前选择的子网关" Bordered="true">
<MDescriptionsItem Label=@CurClient.Description(a=>a.Id)>@CurClient.Id</MDescriptionsItem>
<MDescriptionsItem Label=@CurClient.Description(a=>a.Endpoint)>@CurClient.Endpoint</MDescriptionsItem>
</MDescriptions>
<MDivider></MDivider>
<MRow>
<MCol Cols="12" Md="12">
<div class="m-descriptions-header__title my-2">
导出子网关配置信息
</div>
</MCol>
<MCol Cols="12" Md="12">
<MButton Loading=isDownExport Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="ma-2"
OnClick=@(()=>DBUpload(CurClient))>
导出
</MButton>
</MCol>
</MRow>
<MDivider></MDivider>
<MRow>
<MCol Cols="12" Md="12">
<div class="m-descriptions-header__title my-2">
下发子网关配置信息
</div>
</MCol>
<MCol Cols="12" Md="12">
<MFileInput Label="采集设备Excel" @bind-Value="_importCollectDevicesFile" Style="width:60%;" ShowSize></MFileInput>
<MSwitch Label=@typeof(MqttDBDownRpc).GetDescription(nameof(MqttDBDownRpc.IsCollectDevicesFullUp)) @bind-Value=@IsCollectDevicesFullUp />
</MCol>
<MCol Cols="12" Md="12">
<MFileInput Label="上传设备Excel" @bind-Value="_importUploadDevicesFile" Style="width:60%;" ShowSize></MFileInput>
<MSwitch Label=@typeof(MqttDBDownRpc).GetDescription(nameof(MqttDBDownRpc.IsUploadDevicesFullUp)) @bind-Value=@IsUploadDevicesFullUp />
</MCol>
<MCol Cols="12" Md="12">
<MFileInput Label="变量Excel" @bind-Value="_importDeviceVariablesFile" Style="width:60%;" ShowSize></MFileInput>
<MSwitch Label=@typeof(MqttDBDownRpc).GetDescription(nameof(MqttDBDownRpc.IsDeviceVariablesFullUp)) @bind-Value=@IsDeviceVariablesFullUp />
</MCol>
<MCol Cols="12" Md="12">
<MSwitch Label=@typeof(MqttDBDownRpc).GetDescription(nameof(MqttDBDownRpc.IsRestart)) @bind-Value=@IsRestart />
</MCol>
<MCol Cols="12" Md="12">
<MButton Loading=isDownExport Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="ma-2"
OnClick=@(()=>DBDown(CurClient))>
下发
</MButton>
</MCol>
</MRow>
<MDivider></MDivider>
</MCard>
}
</MCol>
</MRow>
</MCard>
</MExpansionPanelContent>
</MExpansionPanel>
</MExpansionPanels>
</MRow>

View File

@@ -1,209 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BlazorComponent;
using Furion;
using Masa.Blazor;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.JSInterop;
using MQTTnet.Server;
using System.IO;
using System.Threading;
using ThingsGateway.Admin.Core;
using ThingsGateway.Application;
namespace ThingsGateway.Blazor;
/// <summary>
/// ManageGatewayPage
/// </summary>
public partial class ManageGatewayPage
{
List<StringNumber> Panel { get; set; } = new();
readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(5));
ManageGatewayWorker ManageGatewayWorker { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
ManageGatewayWorker = ServiceHelper.GetBackgroundService<ManageGatewayWorker>();
Panel.Add("2");
_ = RunTimerAsync();
base.OnInitialized();
}
private async Task RunTimerAsync()
{
await RefreshAsync();
while (await _periodicTimer.WaitForNextTickAsync())
{
try
{
await RefreshAsync();
await InvokeAsync(StateHasChanged);
}
catch
{
}
}
}
List<MqttClientStatus> CurClients { get; set; }
List<MqttClientStatus> MqttClientStatuses { get; set; } = new();
private async Task RefreshAsync()
{
MqttClientStatuses = await ManageGatewayWorker.GetClientGatewayAsync();
}
/// <inheritdoc/>
public override void Dispose()
{
_periodicTimer?.Dispose();
base.Dispose();
}
IJSObjectReference _helper;
[Inject]
IJSRuntime JS { get; set; }
private bool isDownExport;
private bool IsCollectDevicesFullUp;
private bool IsUploadDevicesFullUp;
private bool IsDeviceVariablesFullUp;
private bool IsRestart;
IBrowserFile _importCollectDevicesFile;
IBrowserFile _importUploadDevicesFile;
IBrowserFile _importDeviceVariablesFile;
/// <summary>
/// 获取子网关配置导出excel
/// </summary>
/// <param name="mqttClientStatus"></param>
/// <returns></returns>
private async Task DBUpload(MqttClientStatus mqttClientStatus)
{
var data = await ManageGatewayWorker.GetClientGatewayDBAsync(mqttClientStatus.Id);
if (data.IsSuccess)
{
isDownExport = true;
await InvokeAsync(StateHasChanged);
if (data.Content.CollectDevices.Count > 0)
{
using var devices = await App.GetService<CollectDeviceService>().ExportFileAsync(data.Content.CollectDevices);
using var streamRef = new DotNetStreamReference(stream: devices);
_helper ??= await JS.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Admin.Blazor.Core/js/downloadFileFromStream.js");
await _helper.InvokeVoidAsync("downloadFileFromStream", $"子网关{mqttClientStatus.Id}采集设备导出{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx", streamRef);
}
else
{
await PopupService.EnqueueSnackbarAsync("无采集设备", AlertTypes.None);
}
if (data.Content.UploadDevices.Count > 0)
{
using var devices = await App.GetService<UploadDeviceService>().ExportFileAsync(data.Content.UploadDevices);
using var streamRef = new DotNetStreamReference(stream: devices);
_helper ??= await JS.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Admin.Blazor.Core/js/downloadFileFromStream.js");
await _helper.InvokeVoidAsync("downloadFileFromStream", $"子网关{mqttClientStatus.Id}上传设备导出{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx", streamRef);
}
else
{
await PopupService.EnqueueSnackbarAsync("无上传设备", AlertTypes.None);
}
if (data.Content.DeviceVariables.Count > 0)
{
using var devices = await App.GetService<VariableService>().ExportFileAsync(data.Content.DeviceVariables);
using var streamRef = new DotNetStreamReference(stream: devices);
_helper ??= await JS.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Admin.Blazor.Core/js/downloadFileFromStream.js");
await _helper.InvokeVoidAsync("downloadFileFromStream", $"子网关{mqttClientStatus.Id}变量导出{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx", streamRef);
}
else
{
await PopupService.EnqueueSnackbarAsync("无采集变量", AlertTypes.None);
}
await PopupService.EnqueueSnackbarAsync("上传成功", AlertTypes.Success);
}
else
{
await PopupService.EnqueueSnackbarAsync(data.Message, AlertTypes.Error);
}
isDownExport = false;
}
/// <summary>
/// 下发子网关配置
/// </summary>
/// <returns></returns>
private async Task DBDown(MqttClientStatus mqttClientStatus)
{
MqttDBDownRpc rpc = new()
{
IsCollectDevicesFullUp = IsCollectDevicesFullUp,
IsDeviceVariablesFullUp = IsDeviceVariablesFullUp,
IsUploadDevicesFullUp = IsUploadDevicesFullUp,
IsRestart = IsRestart
};
if (_importCollectDevicesFile != null)
{
using var fs1 = new MemoryStream();
using var stream1 = _importCollectDevicesFile.OpenReadStream(512000000);
await stream1.CopyToAsync(fs1);
rpc.CollectDevices = fs1.ToArray();
}
if (_importUploadDevicesFile != null)
{
using var fs2 = new MemoryStream();
using var stream2 = _importUploadDevicesFile.OpenReadStream(512000000);
await stream2.CopyToAsync(fs2);
rpc.UploadDevices = fs2.ToArray();
}
if (_importDeviceVariablesFile != null)
{
using var fs3 = new MemoryStream();
using var stream3 = _importDeviceVariablesFile.OpenReadStream(512000000);
await stream3.CopyToAsync(fs3);
rpc.DeviceVariables = fs3.ToArray();
}
var data = await ManageGatewayWorker.SetClientGatewayDBAsync(mqttClientStatus.Id, rpc);
if (data.IsSuccess)
{
await PopupService.EnqueueSnackbarAsync("下发成功", AlertTypes.Success);
}
else
{
await PopupService.EnqueueSnackbarAsync(data.Message, AlertTypes.Error);
}
}
}

View File

@@ -1,480 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/gatewayconfig/memoryvariable"
@namespace ThingsGateway.Blazor
@using System.Linq.Expressions;
@using BlazorComponent;
@using Furion.DataValidation;
@using Mapster;
@using Masa.Blazor.Presets;
@using System.IO;
@using Masa.Blazor;
@using Microsoft.AspNetCore.Authorization;
@using ThingsGateway.Admin.Blazor.Core;
@using ThingsGateway.Admin.Blazor;
@using ThingsGateway.Admin.Core;
@using ThingsGateway.Application;
@using TouchSocket.Core;
@attribute [Authorize]
@inherits BaseComponentBase
@inject UserResoures UserResoures
@layout MainLayout
<AppDataTable @ref="_datatable"
StyleString=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+10}px)")
TItem="DeviceVariable" SearchItem="MemoryVariablePageInput"
AddItem="MemoryVariableAddInput" EditItem="MemoryVariableAddInput"
IsMenuOperTemplate=false SearchModel="search"
FilterHeaders="FilterHeaders"
QueryCallAsync="QueryCallAsync" AddCallAsync="AddCallAsync"
EditCallAsync="EditCallAsync" DeleteCallAsync="DeleteCallAsync"
IsShowDetailButton
IsShowQueryButton
IsShowAddButton=@UserResoures.IsHasButtonWithRole("gatewayvariableadd")
IsShowDeleteButton=@UserResoures.IsHasButtonWithRole("gatewayvariabledelete")
IsShowEditButton=@UserResoures.IsHasButtonWithRole("gatewayvariableedit")>
<SearchTemplate>
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.Name" Clearable
Outlined Label=@context.Description(x => x.Name) />
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.VariableAddress" Clearable
Outlined Label=@context.Description(x => x.VariableAddress) />
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.UploadDeviceName" Clearable
Outlined Label=@context.Description(x => x.UploadDeviceName) />
</SearchTemplate>
<OtherToolbarTemplate>
<MMenu OffsetY
Context="menu">
<ActivatorContent>
<MButton @attributes="@menu.Attrs" Color="primary"
Class="my-1 mx-2 ">
导出
<AppChevronDown></AppChevronDown>
</MButton>
</ActivatorContent>
<ChildContent>
<MList>
<MListItem OnClick="()=>DownExportAsync()"> 导出全部 </MListItem>
<MListItem OnClick="()=>DownExportAsync(search)"> 导出搜索项 </MListItem>
</MList>
</ChildContent>
</MMenu>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewayvariableedit")) Class="my-1 mx-2" OnClick="()=>{ ImportExcel.Step=1; ImportExcel.IsShowImport=true;}" Color="primary">
导入
</MButton>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewayvariableedit")) Class="my-1 mx-2" OnClick=ClearAsync Color="primary">
清空
</MButton>
</OtherToolbarTemplate>
<AddTemplate>
@GetRenderFragment(context)
</AddTemplate>
<EditTemplate>
@GetRenderFragment(context)
</EditTemplate>
</AppDataTable>
<ImportExcel @ref=ImportExcel Import="SaveDeviceImportAsync" Preview="DeviceImportAsync" />
@code {
RenderFragment GetRenderFragment(MemoryVariableAddInput context)
{
RenderFragment renderFragment =
@<div>
<MTabs @bind-Value="tab">
<MTab Value=1 Style="height:50px;"> 基本属性 </MTab>
<MTab Value=2 Style="height:50px;"> 报警属性 </MTab>
<MTab Value=3 Style="height:50px;"> 历史属性 </MTab>
<MTab Value=4 Style="height:50px;"> 上传属性 </MTab>
</MTabs>
<MTabsItems Value="tab">
<MTabItem Value="1">
@if (tab == 1)
{
<MCard Flat Class="ma-2">
<MRow NoGutters Class="my-2" Justify="JustifyTypes.Start">
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.Name)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Name />
</MCol>
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.Description)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Description />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.Unit)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Unit />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.ProtectTypeEnum)) </MSubheader>
<MSelect Class="mr-3" @bind-Value="context.ProtectTypeEnum" Outlined
Items=@(typeof(ProtectTypeEnum).GetEnumList())
MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })"
ItemText=@((u) =>u.des)
ItemValue=@(u =>(ProtectTypeEnum)u.value)
HideDetails=@("auto") Height="30"
Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.ReadExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.ReadExpressions />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.WriteExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.WriteExpressions />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.RpcWriteEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.RpcWriteEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
</MRow>
</MCard>
}
</MTabItem>
<MTabItem Value="2">
@if (tab == 2)
{
<MCard Flat Class="ma-2">
<MRow NoGutters Class="my-2" Justify="JustifyTypes.Center">
<MCol Md=12 Cols=12 class="px-1 py-2">
<MDivider Center Height="2"></MDivider>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolCloseAlarmEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.BoolCloseAlarmEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolCloseAlarmText)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.BoolCloseAlarmText />
</MCol>
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolCloseRestrainExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.BoolCloseRestrainExpressions />
</MCol>
<MCol Md=12 Cols=12 class="px-1 py-2">
<MDivider Center Height="2"></MDivider>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolOpenAlarmEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.BoolOpenAlarmEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolOpenAlarmText)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.BoolOpenAlarmText />
</MCol>
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolOpenRestrainExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.BoolOpenRestrainExpressions />
</MCol>
<MCol Md=12 Cols=12 class="px-1 py-2">
<MDivider Center Height="2"></MDivider>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HHAlarmEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.HHAlarmEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HHAlarmText)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HHAlarmText />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HHAlarmCode)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HHAlarmCode />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HHRestrainExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HHRestrainExpressions />
</MCol>
<MCol Md=12 Cols=12 class="px-1 py-2">
<MDivider Center Height="2"></MDivider>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HAlarmEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.HAlarmEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HAlarmText)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HAlarmText />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HAlarmCode)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HAlarmCode />
</MCol>
<MCol Md=6 Cols=12 class="px-1 py-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HRestrainExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HRestrainExpressions />
</MCol>
<MCol Md=12 Cols=12 class="px-1 py-2">
<MDivider Center Height="2"></MDivider>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LAlarmEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.LAlarmEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LAlarmText)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LAlarmText />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LAlarmCode)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LAlarmCode />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LRestrainExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LRestrainExpressions />
</MCol>
<MCol Md=12 Cols=12 class="px-1 py-2">
<MDivider Center Height="2"></MDivider>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LLAlarmEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.LLAlarmEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LLAlarmText)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LLAlarmText />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LLAlarmCode)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LLAlarmCode />
</MCol>
<MCol Md=6 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.LLRestrainExpressions)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LLRestrainExpressions />
</MCol>
</MRow>
</MCard>
}
</MTabItem>
<MTabItem Value="3">
@if (tab == 3)
{
<MCard Flat Class="ma-2">
<MRow NoGutters Class="my-2" Justify="JustifyTypes.Center">
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HisEnable)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.HisEnable Outlined
Items=@(new List<bool>(){true,false}) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u)
HideDetails=@("auto") Height="30" Dense>
</MSelect>
</MCol>
<MCol Md=12 Cols=12 class="px-1">
<MSubheader Class="font-weight-black"> @(context.Description(x => x.HisType)) </MSubheader>
<MSelect Class="mr-3" @bind-Value=@context.HisType Outlined
Items=@(typeof(HisType).GetEnumList()) Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.des)
ItemValue=@(u =>(HisType)u.value)
HideDetails=@("auto") Height="30"
Dense>
</MSelect>
</MCol>
</MRow>
</MCard>
}
</MTabItem>
<MTabItem Value="4">
@if (tab == 4)
{
<MCard Flat Class="pa-2">
<MRow Class="px-1 py-6" Align="AlignTypes.Center">
<MSelect Class="mr-3" @bind-Value=@choiceUploadDeviceId
Outlined
Items=@(UploadDevices)
Clearable
MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })"
ItemText=@((u) =>u.Name) ItemValue=@(u =>u.Id)
HideDetails=@("auto") Height="30"
Dense>
</MSelect>
<MButton Class="my-3" OnClick=@(async() =>
{
if(choiceUploadDeviceId>0)
{
var data=GetDriverProperties(UploadDevices.FirstOrDefault(a=>a.Id==choiceUploadDeviceId).PluginId,context.VariablePropertys.ContainsKey(choiceUploadDeviceId)?context.VariablePropertys[choiceUploadDeviceId]:null);
if(data?.Count>0)
{
context.VariablePropertys.AddOrUpdate(choiceUploadDeviceId,a=> data,(a,b)=>data);
}
else
{
await PopupService.EnqueueSnackbarAsync("此上传设备没有变量上传属性",AlertTypes.Warning);
context.VariablePropertys.Remove(choiceUploadDeviceId);
}
}
else
{
await PopupService.EnqueueSnackbarAsync("需选择上传设备",AlertTypes.Warning);
}
}
) Color="primary">
添加/刷新属性
</MButton>
</MRow>
@if (context.VariablePropertys != null)
{
@foreach (var item in context.VariablePropertys)
{
<MCard Class="pa-2 my-3">
<MSubheader Class="mt-4 font-weight-black">
@(
UploadDevices.FirstOrDefault(a => a.Id == item.Key)?.Name ?? "未知"
)
</MSubheader>
@foreach (var property in item.Value ?? new())
{
<MSubheader Class="mt-4 font-weight-black"> @property.Description </MSubheader>
<MTooltip Disabled=string.IsNullOrEmpty(property.Remark)
Bottom
Context="tip">
<ActivatorContent>
<MTextField @attributes="@tip.Attrs"
Dense
Outlined
HideDetails="@("auto")" @bind-Value=@property.Value />
</ActivatorContent>
<ChildContent>
<span>@property.Remark</span>
</ChildContent>
</MTooltip>
}
<MCardActions>
<MButton Class="my-3" OnClick=@(() =>
{
context.VariablePropertys.Remove(item.Key);
}
) Color="primary">
删除
</MButton>
</MCardActions>
</MCard>
}
}
</MCard>
}
</MTabItem>
</MTabsItems>
</div>
;
return renderFragment;
}
}

View File

@@ -1,126 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BlazorComponent;
using Furion;
using Mapster;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Admin.Core;
using ThingsGateway.Application;
namespace ThingsGateway.Blazor;
/// <summary>
/// 内存变量页面
/// </summary>
public partial class MemoryVariablePage
{
private IAppDataTable _datatable;
long choiceUploadDeviceId;
ImportExcel ImportExcel;
private readonly MemoryVariablePageInput search = new();
StringNumber tab;
List<UploadDevice> UploadDevices = new();
[Inject]
AjaxService AjaxService { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override async Task OnParametersSetAsync()
{
UploadDevices = App.GetService<IUploadDeviceService>().GetCacheList();
await base.OnParametersSetAsync();
}
private void FilterHeaders(List<DataTableHeader<DeviceVariable>> datas)
{
datas.RemoveWhere(it => it.Value == nameof(DeviceVariable.DeviceId));
datas.RemoveWhere(it => it.Value == nameof(DeviceVariable.VariableAddress));
datas.RemoveWhere(it => it.Value == nameof(DeviceVariable.OtherMethod));
}
private async Task AddCallAsync(MemoryVariableAddInput input)
{
await App.GetService<VariableService>().AddAsync(input);
}
private async Task ClearAsync()
{
var confirm = await PopupService.OpenConfirmDialogAsync("确认", "清空?");
if (confirm)
{
await App.GetService<VariableService>().ClearMemoryVariableAsync();
}
await DatatableQueryAsync();
}
private async Task DatatableQueryAsync()
{
await _datatable.QueryClickAsync();
}
private async Task DeleteCallAsync(IEnumerable<DeviceVariable> input)
{
await App.GetService<VariableService>().DeleteAsync(input.Select(a => a.Id).ToArray());
}
Task<Dictionary<string, ImportPreviewOutputBase>> DeviceImportAsync(IBrowserFile file)
{
return App.GetService<VariableService>().MemoryVariablePreviewAsync(file);
}
async Task DownExportAsync(MemoryVariablePageInput input = null)
{
await AjaxService.DownFileAsync("gatewayFile/memoryVariable", SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat(), input.Adapt<MemoryVariableInput>());
}
private async Task EditCallAsync(MemoryVariableAddInput input)
{
await App.GetService<VariableService>().EditAsync(input);
}
List<DependencyProperty> GetDriverProperties(long driverId, List<DependencyProperty> dependencyProperties)
{
return ServiceHelper.GetBackgroundService<UploadDeviceWorker>().GetVariablePropertys(driverId, dependencyProperties);
}
private async Task<SqlSugarPagedList<DeviceVariable>> QueryCallAsync(MemoryVariablePageInput input)
{
var data = await App.GetService<VariableService>().PageAsync(input);
return data;
}
async Task SaveDeviceImportAsync(Dictionary<string, ImportPreviewOutputBase> data)
{
await App.GetService<VariableService>().ImportAsync(data);
await DatatableQueryAsync();
ImportExcel.IsShowImport = false;
}
}

View File

@@ -1,264 +0,0 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/gatewayconfig/uploaddevice"
@namespace ThingsGateway.Blazor
@using System.Linq.Expressions;
@using BlazorComponent;
@using Furion;
@using Mapster;
@using Masa.Blazor.Presets;
@using System.IO;
@using Masa.Blazor;
@using Microsoft.AspNetCore.Authorization;
@using ThingsGateway.Admin.Blazor.Core;
@using ThingsGateway.Admin.Blazor;
@using ThingsGateway.Admin.Core;
@using ThingsGateway.Application;
@attribute [Authorize]
@inherits BaseComponentBase
@inject UserResoures UserResoures
@layout MainLayout
@if (IsMobile)
{
@GetAppDataTable()
}
else
{
<MRow>
<MCol Md=2 Cols="12">
<MCard Class="ma-2" Height=@("100%")>
<MCardTitle>
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="mx-2 my-1" @bind-Value="_searchName"
Outlined Label=@typeof(CollectDevice).GetDescription(nameof(CollectDevice.DeviceGroup)) />
</MCardTitle>
<MTreeview Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+100}px);overflow-y:auto")
Dense TItem="string" TKey="string" ActiveChanged=@(async a=>
{
if(search.DeviceGroup!=a.FirstOrDefault())
{
search.DeviceGroup=a.FirstOrDefault();
await DatatableQuery();
}
} )
Items="_deviceGroups" ItemText="r=>r" ItemChildren="r=>null"
Search="@_searchName"
Activatable ItemKey=@(r=>r)>
<LabelContent>
<span title=@context.Item>
@context.Item
</span>
</LabelContent>
</MTreeview>
</MCard>
</MCol>
<MCol Md=10 Cols="12">
@GetAppDataTable( )
</MCol>
</MRow>
}
<ImportExcel @ref=ImportExcel Import="SaveDeviceImportAsync" Preview="DeviceImportAsync" />
@code {
RenderFragment GetAppDataTable()
{
RenderFragment renderFragment =
@<AppDataTable @ref="_datatable" TItem="UploadDevice" SearchItem="UploadDevicePageInput"
StyleString=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+10}px)")
AddItem="UploadDeviceAddInput" EditItem="UploadDeviceEditInput"
IsMenuOperTemplate=false SearchModel="search"
QueryCallAsync="QueryCallAsync" AddCallAsync="AddCallAsync"
EditCallAsync="EditCallAsync" DeleteCallAsync="DeleteCallAsync"
IsShowDetailButton
IsShowQueryButton
IsShowAddButton=@UserResoures.IsHasButtonWithRole("gatewayuploaddeviceadd")
IsShowDeleteButton=@UserResoures.IsHasButtonWithRole("gatewayuploaddevicedelete")
IsShowEditButton=@UserResoures.IsHasButtonWithRole("gatewayuploaddeviceedit")>
<SearchTemplate>
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.Name" Clearable
Outlined Label=@context.Description(x => x.Name) />
<MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 " @bind-Value="context.PluginName" Clearable
Outlined Label=@context.Description(x => x.PluginName) />
</SearchTemplate>
<OtherToolbarTemplate>
<MMenu OffsetY Context="menu">
<ActivatorContent>
<MButton @attributes="@menu.Attrs" Color="primary"
Class="my-1 mx-2 ">
复制
<AppChevronDown></AppChevronDown>
</MButton>
</ActivatorContent>
<ChildContent>
<MList>
<MListItem OnClick="()=>CopyDevice(context)"> 复制设备 </MListItem>
</MList>
</ChildContent>
</MMenu>
<MMenu OffsetY Context="menu">
<ActivatorContent>
<MButton @attributes="@menu.Attrs" Color="primary"
Class="my-1 mx-2 ">
导出
<AppChevronDown></AppChevronDown>
</MButton>
</ActivatorContent>
<ChildContent>
<MList>
<MListItem OnClick="()=>DownExportAsync()"> 导出全部</MListItem>
<MListItem OnClick="()=>DownExportAsync(search)"> 导出搜索项 </MListItem>
</MList>
</ChildContent>
</MMenu>
<MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewayuploaddeviceedit")) Class="my-1 mx-2" OnClick="()=>{ ImportExcel.Step=1; ImportExcel.IsShowImport=true;}" Color="primary">
导入
</MButton>
</OtherToolbarTemplate>
<AddTemplate>
@GetRenderFragment(context)
</AddTemplate>
<EditTemplate>
@GetRenderFragment(context)
</EditTemplate>
<ItemColTemplate>
@switch (context.Header.Value)
{
case nameof(context.Item.Enable):
<EnableChip Value="context.Item.Enable">
</EnableChip>
break;
case nameof(context.Item.PluginId):
<span title=@context.Value>
@(
App.GetService<DriverPluginService>().GetNameById(context.Item.PluginId)
)
</span>
break;
default:
@if (context.Header.CellClass?.Contains("text-truncate") == true)
{
<span title=@context.Value>
@context.Value
</span>
}
else
{
@context.Value
}
break;
}
</ItemColTemplate>
</AppDataTable>
;
return renderFragment;
}
RenderFragment GetRenderFragment(UploadDeviceEditInput context)
{
RenderFragment renderFragment = null;
renderFragment +=
@<div>
<MTabs @bind-Value="tab">
<MTab Value="1" Style="height:50px;"> 基本信息 </MTab>
<MTab Value="2"> 扩展属性 </MTab>
</MTabs>
<MTabsItems Value="tab">
<MTabItem Value="1">
<MCard Flat Class="ma-2">
<MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.Name)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Name />
<MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.Description)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Description />
<MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.DeviceGroup)) </MSubheader>
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.DeviceGroup />
<MSubheader Class="mt-4 font-weight-black"> @(typeof(UploadDeviceRunTime).GetDescription(nameof(UploadDeviceRunTime.PluginName))) </MSubheader>
<MCascader Value="context.PluginId" Class="mt-3 mr-3" Clearable TValue=long TItem="DriverPluginCategory"
ValueChanged=@(a=>DriverValueChangedAsync(context,a))
Items="DriverPlugins" ItemText="u => u.Name" ItemValue="u => u.Id" ItemChildren="u => u.Children"
MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })"
ShowAllLevels="false" HideDetails="@("auto")" Height="30" Dense>
</MCascader>
<MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.Enable)) </MSubheader>
<MSwitch @bind-Value=@context.Enable />
<MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.IsLogOut)) </MSubheader>
<MSwitch @bind-Value=@context.IsLogOut />
</MCard>
</MTabItem>
<MTabItem Value="2">
<MCard Flat Class="ma-2">
<MButton Class="my-3" OnClick=@(async() =>
{
if(context.PluginId>0)
{
context.DevicePropertys= GetDriverProperties(context.PluginId,context.Id);
}
else
{
await PopupService.EnqueueSnackbarAsync("需选择驱动",AlertTypes.Error);
}
}
) Color="primary">
刷新设备属性
</MButton>
@if (context.DevicePropertys != null)
{
@foreach (var item in context.DevicePropertys)
{
<MSubheader Class="mt-4 font-weight-black"> @item.Description </MSubheader>
<MTooltip Disabled=item.Remark.IsNullOrEmpty() Bottom Context="tip">
<ActivatorContent>
@if (item.PropertyName.Contains("BigText"))
{
<MTextarea AutoGrow Dense @attributes="@tip.Attrs" Outlined HideDetails="@("auto")" @bind-Value=@item.Value />
}
else
{
<MTextField Type="@(item.PropertyName.Contains("Password") ? "password" : "text")" @attributes="@tip.Attrs" Dense Outlined HideDetails="@("auto")" @bind-Value=@item.Value />
}
</ActivatorContent>
<ChildContent>
<span>@item.Remark</span>
</ChildContent>
</MTooltip>
}
}
</MCard>
</MTabItem>
</MTabsItems>
</div>
;
return renderFragment;
}
}

View File

@@ -1,149 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BlazorComponent;
using Furion;
using Mapster;
using Masa.Blazor;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using SqlSugar;
using ThingsGateway.Admin.Blazor;
using ThingsGateway.Admin.Blazor.Core;
using ThingsGateway.Admin.Core;
using ThingsGateway.Application;
namespace ThingsGateway.Blazor;
/// <summary>
/// 上传设备页
/// </summary>
public partial class UploadDevicePage
{
private readonly UploadDevicePageInput search = new();
private IAppDataTable _datatable;
List<string> _deviceGroups = new();
string _searchName;
List<DriverPluginCategory> DriverPlugins;
ImportExcel ImportExcel;
StringNumber tab;
[Inject]
AjaxService AjaxService { get; set; }
[CascadingParameter]
MainLayout MainLayout { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override async Task OnParametersSetAsync()
{
DriverPlugins = App.GetService<IDriverPluginService>().GetDriverPluginChildrenList(DriverEnum.Upload);
_deviceGroups = App.GetService<IUploadDeviceService>().GetCacheList()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList();
await base.OnParametersSetAsync();
}
private async Task AddCallAsync(UploadDeviceAddInput input)
{
await App.GetService<UploadDeviceService>().AddAsync(input);
_deviceGroups = App.GetService<UploadDeviceService>().GetCacheList()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList();
await MainLayout.StateHasChangedAsync();
}
async Task CopyDevice(IEnumerable<UploadDevice> data)
{
if (!data.Any())
{
await PopupService.EnqueueSnackbarAsync("需选择一项或多项", AlertTypes.Warning);
return;
}
await App.GetService<UploadDeviceService>().CopyDevAsync(data);
await DatatableQuery();
await PopupService.EnqueueSnackbarAsync("复制成功", AlertTypes.Success);
await MainLayout.StateHasChangedAsync();
}
private async Task DatatableQuery()
{
await _datatable?.QueryClickAsync();
}
private async Task DeleteCallAsync(IEnumerable<UploadDevice> input)
{
await App.GetService<UploadDeviceService>().DeleteAsync(input.Select(a => a.Id).ToArray());
_deviceGroups = App.GetService<UploadDeviceService>().GetCacheList()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList();
await MainLayout.StateHasChangedAsync();
}
Task<Dictionary<string, ImportPreviewOutputBase>> DeviceImportAsync(IBrowserFile file)
{
return App.GetService<UploadDeviceService>().PreviewAsync(file);
}
async Task DownExportAsync(UploadDevicePageInput input = null)
{
await AjaxService.DownFileAsync("gatewayFile/uploadDevice", SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat(), input.Adapt<CollectDeviceInput>());
}
private async Task DriverValueChangedAsync(UploadDeviceEditInput context, long pluginId)
{
if (pluginId <= 0)
return;
if (context.DevicePropertys == null || context.DevicePropertys?.Count == 0)
{
try
{
context.PluginId = pluginId;
context.DevicePropertys = GetDriverProperties(pluginId, context.Id);
await PopupService.EnqueueSnackbarAsync("插件附加属性已更新", AlertTypes.Success);
}
catch (Exception ex)
{
await PopupService.EnqueueSnackbarAsync(ex.Message, AlertTypes.Error);
}
}
}
private async Task EditCallAsync(UploadDeviceEditInput input)
{
await App.GetService<UploadDeviceService>().EditAsync(input);
_deviceGroups = App.GetService<UploadDeviceService>().GetCacheList()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList();
await MainLayout.StateHasChangedAsync();
}
List<DependencyProperty> GetDriverProperties(long driverId, long devId)
{
return ServiceHelper.GetBackgroundService<UploadDeviceWorker>().GetDevicePropertys(driverId, devId);
}
private async Task<SqlSugarPagedList<UploadDevice>> QueryCallAsync(UploadDevicePageInput input)
{
var data = await App.GetService<UploadDeviceService>().PageAsync(input);
return data;
}
async Task SaveDeviceImportAsync(Dictionary<string, ImportPreviewOutputBase> data)
{
await App.GetService<UploadDeviceService>().ImportAsync(data);
await DatatableQuery();
ImportExcel.IsShowImport = false;
await MainLayout.StateHasChangedAsync();
}
}

View File

@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Admin.Blazor\ThingsGateway.Admin.Blazor.csproj" />
<ProjectReference Include="..\ThingsGateway.Application\ThingsGateway.Application.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show More