Compare commits

..

805 Commits

Author SHA1 Message Date
Kimdiego2098
57dfc22ff7 更新版本号 2023-12-08 10:20:54 +08:00
Kimdiego2098
adc489b6ed 回退sqlsugar版本 2023-12-07 15:12:47 +08:00
Kimdiego2098
0623684315 nuget安装sqlite8.0.0 2023-12-07 14:34:14 +08:00
Kimdiego2098
68afccd859 修复OPC节点空间浏览窗口退出时报错的问题 2023-12-07 14:21:24 +08:00
Kimdiego2098
99eef8fb28 更新依赖包 2023-12-06 19:14:42 +08:00
Kimdiego2098
47e7685d39 串口代码整理 2023-12-06 18:54:33 +08:00
Kimdiego2098
b6ea596ade 整理代码 2023-12-06 18:35:52 +08:00
Kimdiego2098
44d60b469b 同步ts 2023-12-06 15:48:55 +08:00
Kimdiego2098
51087408df 同步ts 2023-12-06 15:16:54 +08:00
Kimdiego2098
96226d9e6e 同步ts 2023-12-06 14:56:12 +08:00
Kimdiego2098
28f0f62424 同步ts 2023-12-06 11:35:57 +08:00
Kimdiego2098
2e772a8cd4 同步touchsocket 2023-12-06 09:16:00 +08:00
Kimdiego2098
3fd192f0cf 变量表达式编辑 改为 多行文本框 2023-12-06 07:42:07 +08:00
Kimdiego2098
d16ae81961 更新demo;更新kafka 生成事件; 2023-12-04 20:28:36 +08:00
Kimdiego2098
211a8e093e 更新版本号 2023-12-01 21:23:51 +08:00
Kimdiego2098
84010a51d6 更改方法参数原;现由,分割 2023-12-01 21:22:27 +08:00
Kimdiego2098
e9700ea19b 更新依赖包 2023-12-01 19:55:17 +08:00
Kimdiego2098
e81ec68072 添加解决方案项 2023-11-30 17:44:26 +08:00
Kimdiego2098
6096e97708 modbusDtu更名 2023-11-30 10:47:37 +08:00
Kimdiego2098
6f302b66b4 添加ModbusRtu格式的DTU驱动 2023-11-30 10:16:26 +08:00
Kimdiego2098
b6e41890f7 同步TouchSocket 2023-11-30 10:16:20 +08:00
Kimdiego2098
be5332a048 添加离线后恢复间隔时间选项,添加重试次数选项 2023-11-29 20:47:23 +08:00
Kimdiego2098
4d72aeb0cd 修复种子数据带转义字符串时错误解析的问题 2023-11-29 19:04:56 +08:00
Kimdiego2098
c7c3c35100 补充git漏掉的props文件 2023-11-29 13:37:52 +08:00
Kimdiego2098
2f0df3c552 修复aop拦截不生效的问题,更新版本号,整理props文件 2023-11-29 12:43:05 +08:00
Kimdiego2098
e6effcd921 更新依赖包 2023-11-28 22:06:43 +08:00
Kimdiego2098
cd462b9523 修正导出设备表的sheet名称 2023-11-28 09:30:47 +08:00
Kimdiego2098
7e2e17d38b 整理部分底层代码 2023-11-27 16:17:12 +08:00
Kimdiego2098
665d616a06 整理部分项目项 2023-11-27 11:11:15 +08:00
Kimdiego2098
ced90f5bb9 更新版本号 2023-11-24 14:48:53 +08:00
Kimdiego2098
ca0ddb6cb4 10功能码写入时 修改需判断的crc长度 2023-11-24 14:48:28 +08:00
Kimdiego2098
3d733813e1 修复 modbusTcpDtu 初始化 空指针错误 2023-11-24 09:44:47 +08:00
Kimdiego2098
45ddc0b154 OPCUA插件afterStop方法重写时需执行父类方法 2023-11-23 20:51:08 +08:00
Kimdiego2098
87294497f3 调整网关状态页面 2023-11-23 20:49:35 +08:00
Kimdiego2098
5f45f9c0d0 修改内存缓存mapster选项 2023-11-23 15:22:39 +08:00
Kimdiego2098
7f78c06793 添加kafka的SecurityProtocol与SaslMechanism参数说明 2023-11-22 21:29:08 +08:00
Kimdiego2098
8ea5b7eebc 修复上个提交导致的excel导入产生空指针错误 2023-11-22 20:52:58 +08:00
Kimdiego2098
e49cd511af excel导入导出设备表时,去除多余的字段 2023-11-22 16:09:41 +08:00
Kimdiego2098
15c88646df socktClient重载toString 2023-11-22 15:28:04 +08:00
Kimdiego2098
f47919543d 更新版本号 2023-11-22 14:06:52 +08:00
Kimdiego2098
a95291d9cd 代码文件编码更改 2023-11-22 14:02:41 +08:00
Kimdiego2098
033deb3d29 修复变量上传属性在编辑页面不显示的问题 2023-11-22 13:35:44 +08:00
Kimdiego2098
923994c1f4 修复变量上传属性在编辑页面不显示的问题 2023-11-22 13:30:42 +08:00
Kimdiego2098
3d45b839b4 去除不存在的菜单 2023-11-21 18:19:14 +08:00
Kimdiego2098
55d1efa212 调整循环延时时间限值 2023-11-21 16:27:11 +08:00
Kimdiego2098
21d794a0e5 调试mqtt依赖 2023-11-21 15:07:07 +08:00
Kimdiego2098
614022a78c 去除上传间隔1s限制 2023-11-21 11:46:33 +08:00
Kimdiego2098
c00688e23a 整理代码 2023-11-21 11:41:44 +08:00
Kimdiego2098
5c69917d19 更新依赖 2023-11-21 08:44:47 +08:00
Kimdiego2098
6a5eb75b6c 更新版本号 2023-11-20 23:05:12 +08:00
Kimdiego2098
727c55eaa2 去除treeview 2023-11-20 23:04:54 +08:00
Kimdiego2098
9b03173ec5 去除treeview显示 2023-11-20 22:53:34 +08:00
Kimdiego2098
ab95855d6d 更新版本号 2023-11-20 22:47:18 +08:00
Kimdiego2098
bb84594c6b mqttserver/client插件支持websocket通道,直接对接前端 2023-11-20 22:42:08 +08:00
Kimdiego2098
97392c76b1 mqttserver/client插件支持websocket通道,直接对接前端 2023-11-20 22:33:26 +08:00
Kimdiego2098
53aec2b306 modbusTcpServer最大连接数设为3w 2023-11-20 19:46:07 +08:00
Kimdiego2098
a0a381dc63 更新依赖 2023-11-20 17:35:42 +08:00
Kimdiego2098
4fa95edeec 添加admin-解决方案 2023-11-20 15:38:33 +08:00
Kimdiego2098
b944f1d70e 修复设备curd服务aop失效的问题 2023-11-19 23:30:11 +08:00
Kimdiego2098
0f1a5d0085 更新版本号 2023-11-19 22:32:53 +08:00
Kimdiego2098
e7e10222e7 modbus server 添加是否立即写入内存的选项 2023-11-19 22:32:13 +08:00
Kimdiego2098
87c4fda588 默认开启多标签 2023-11-19 22:31:47 +08:00
Kimdiego2098
0ff820de6f 添加历史报警插件 2023-11-19 22:19:05 +08:00
Kimdiego2098
0dcf55e6d9 修复4.0代码 s7协议未设置适配器导致初始化/读写失败的问题 2023-11-19 21:49:32 +08:00
Kimdiego2098
a69842c910 增加上传插件 缓存基类 2023-11-19 15:20:01 +08:00
Kimdiego2098
318f635e4d 获取插件继承属性时,去除不重写特性的属性 2023-11-19 14:15:58 +08:00
Kimdiego2098
abf0d72316 整理文件 2023-11-18 23:04:51 +08:00
Kimdiego2098
5cd89c8844 整理文件 2023-11-18 23:04:25 +08:00
Kimdiego2098
501c1af38c 整理文件 2023-11-18 23:03:24 +08:00
Kimdiego2098
8526e130d9 整理文件 2023-11-18 23:03:02 +08:00
Kimdiego2098
76fd08eade 整理文件 2023-11-18 23:02:28 +08:00
Kimdiego2098
bfe48ae9d2 整理文件 2023-11-18 23:01:56 +08:00
Kimdiego2098
8547cdba7c 去除不必要的文件 2023-11-18 22:52:26 +08:00
Kimdiego2098
00d0cd631d 去除不必要的文件 2023-11-18 22:50:03 +08:00
Kimdiego2098
04460d13cd 迁移4.0 2023-11-18 22:45:24 +08:00
Kimdiego2098
a17a15e5d7 迁移4.0 2023-11-18 22:43:36 +08:00
Kimdiego2098
bfe2465658 添加停止线程等待时间 2023-11-16 22:32:22 +08:00
Kimdiego2098
4568987785 s7握手失败后,手动关闭连接 2023-11-11 10:26:05 +08:00
Kimdiego2098
c0e5a1419d fix:修复线程阻塞检测触发重启时,后台变量列表不再刷新的问题! 2023-11-10 09:00:50 +08:00
Kimdiego2098
23ac7ab748 历史数据上传成功后,才上传缓存数据 2023-11-09 18:55:56 +08:00
Kimdiego2098
5622dc74a8 update 2023-11-08 16:19:46 +08:00
Kimdiego2098
528986f4f0 发布3.0.1版本 2023-11-03 11:32:13 +08:00
Kimdiego2098
e6b8ff3f91 update touchsocket 2023-11-03 11:27:12 +08:00
Kimdiego2098
3fc3c5296f update touchsocket 2023-11-03 11:19:26 +08:00
Kimdiego2098
f4f2a14a31 不存在插件时,报错内容优化 2023-11-01 14:51:08 +08:00
Kimdiego2098
fa3f464cb6 update webapiClient 2023-11-01 14:21:53 +08:00
Kimdiego2098
bed20538ae 调整Timeout属性为int数据类型 2023-10-31 23:49:46 +08:00
Kimdiego2098
3da461c6e6 更新发布版3.0.0.28 2023-10-31 17:47:07 +08:00
Kimdiego2098
9c73e8452a 更新OPCUAClient,订阅模式适配 分组 2023-10-31 10:55:50 +08:00
Kimdiego2098
750dab69d3 update 2023-10-31 00:24:00 +08:00
Kimdiego2098
702d0da47c tcpservice停止时执行shutdown方法,修复demo发布不显示页面的问题 2023-10-30 21:31:30 +08:00
Kimdiego2098
3a955b1e53 添加部分Pro内容 2023-10-29 19:39:38 +08:00
Kimdiego2098
d408bd93e8 添加部分Pro内容 2023-10-29 19:34:17 +08:00
Kimdiego2098
63fd9eb1de 更新文档 2023-10-28 22:06:58 +08:00
Kimdiego2098
4718ab6ddf 发布3.0.0.27版本;
优化设备线程启停逻辑
添加winform(blazor) demo
部分页面显示内容优化
2023-10-28 22:03:52 +08:00
Kimdiego2098
affd4b3a26 发布3.0.0.27版本;
优化设备线程启停逻辑
添加winform(blazor) demo
部分页面显示内容优化
2023-10-28 22:00:35 +08:00
Kimdiego2098
6a660dbcda 优化设备线程启停逻辑 2023-10-28 21:58:09 +08:00
Kimdiego2098
5ff3af8b48 添加部分Pro内容 2023-10-28 21:21:03 +08:00
Kimdiego2098
9d49070f93 添加winform(blazor) demo 2023-10-28 15:20:12 +08:00
Kimdiego2098
5b3994a1dd 调整pro类库 2023-10-28 10:54:26 +08:00
Diego2098
bb1e62c580 设备状态页面显示优化 2023-10-27 20:19:26 +08:00
Diego2098
29cc776908 状态显示:已退出自动更新 2023-10-27 20:18:57 +08:00
Kimdiego2098
d2ef60b2ec 报文显示限长500 2023-10-27 14:53:19 +08:00
Kimdiego2098
41ea496d41 tcpserver 报文输出 添加 ip端口 提示 2023-10-27 14:41:35 +08:00
Kimdiego2098
c69bb38929 update the tcpserver class and add log output for start and stop 2023-10-27 14:24:26 +08:00
Kimdiego2098
23b3f8494e 优化线程上下文转换 2023-10-26 19:16:24 +08:00
Kimdiego2098
0ff94eec1b 添加ConfigureAwait 2023-10-26 18:06:33 +08:00
Kimdiego2098
f67a638565 优化设备导出逻辑 2023-10-26 15:15:43 +08:00
Kimdiego2098
778cf71c61 优化设备导出逻辑 2023-10-26 15:10:26 +08:00
Kimdiego2098
e2da083b48 3.0.0.25 2023-10-26 13:21:40 +08:00
Kimdiego2098
dcc48c0d4b Merge branch 'master' of https://gitee.com/dotnetchina/ThingsGateway 2023-10-26 13:20:54 +08:00
Kimdiego2098
2446b4a591 3.0.0.25 2023-10-26 13:20:31 +08:00
Kimdiego2098
a973d633c8 update 2023-10-26 13:19:18 +08:00
Kimdiego2098
c3b11e6e0f s7添加错误返回码提示,组包处理 2023-10-26 13:05:06 +08:00
Kimdiego2098
44bec2d99e 增加硬件信息 相关json配置 2023-10-26 10:54:48 +08:00
Kimdiego2098
0d59d0a3f6 更新nuget依赖 2023-10-26 09:37:01 +08:00
Kimdiego2098
ddc8372b09 硬件信息页面添加刷新条件 2023-10-26 09:20:25 +08:00
Kimdiego2098
ea7325f4e0 更新文档 2023-10-25 00:53:27 +08:00
Kimdiego2098
6048f95415 update touchsocket 2023-10-25 00:53:17 +08:00
Kimdiego2098
5682705b8d <Version>3.0.0.24</Version> 2023-10-24 23:48:20 +08:00
Kimdiego2098
6c3b82607d 更改S7协议 设备属性,注意删除了DstTSAP,改为较直观的 机架号/槽位号 2023-10-24 23:47:35 +08:00
Kimdiego2098
486c9f4ebf 添加属性识别 16进制写入 2023-10-24 23:46:50 +08:00
Kimdiego2098
7c8adebf2d 添加nuget依赖包 2023-10-24 20:50:39 +08:00
Kimdiego2098
ce98bb0dc1 更新nuget包 2023-10-24 00:37:46 +08:00
Kimdiego2098
60b49a4296 调整解决方案文件夹 2023-10-24 00:13:03 +08:00
Kimdiego2098
5d0a2ffae0 暂时屏蔽mqttserver 遗留消息错误 2023-10-23 20:51:11 +08:00
Kimdiego2098
bbcf7c722a 调整编码 2023-10-23 20:47:00 +08:00
Kimdiego2098
66574eec6d 调整mqttlog 2023-10-23 20:46:17 +08:00
Kimdiego2098
305df3a42c 调整mqttlog 2023-10-23 20:43:58 +08:00
Kimdiego2098
323c6f4270 添加部分兼容方法 2023-10-22 02:42:16 +08:00
Kimdiego2098
c056b2b14c 添加部分兼容方法 2023-10-22 02:26:18 +08:00
Kimdiego2098
02ebb07fd8 update touchsocket 2023-10-22 02:26:04 +08:00
Kimdiego2098
59db0890c3 调整代码格式 2023-10-21 19:15:26 +08:00
Kimdiego2098
3ea0d9ed3a update touchsocket 2023-10-21 19:08:21 +08:00
Kimdiego2098
0371d11dd5 调整代码格式 2023-10-21 19:03:15 +08:00
Kimdiego2098
12f1ebaee9 调整代码格式 2023-10-21 19:02:58 +08:00
Kimdiego2098
5202cc22d1 3.0.0.23 2023-10-20 21:38:22 +08:00
Kimdiego2098
07a34bdcbf 优化SQLDB实时表模式,插入/更新 2023-10-20 21:38:07 +08:00
Kimdiego2098
27806da4be sqlsugar提示默认中文 2023-10-20 21:37:42 +08:00
Kimdiego2098
a15dd1a3ce update touchsocket 2023-10-20 21:03:29 +08:00
Kimdiego2098
6cc7451c7c 优化 ModbusServer 内存占用 2023-10-20 01:53:48 +08:00
Kimdiego2098
652aeee782 3.0.0.21 2023-10-20 01:19:18 +08:00
Kimdiego2098
8e1e887b8f 优化OPCUAServer,取消注册,提供多url写入 2023-10-20 01:19:05 +08:00
Kimdiego2098
6601a2de64 更新赞助名单 2023-10-19 21:15:35 +08:00
Kimdiego2098
75a42ffc32 更新文档 2023-10-19 20:36:28 +08:00
Kimdiego2098
8a80a52f5f 更新版本:3.0.0.20 2023-10-19 20:27:31 +08:00
Kimdiego2098
b373a434ba 优化modbusServer内存管理 2023-10-19 20:24:39 +08:00
Kimdiego2098
cd78fdafe6 update touchsocket 2023-10-19 20:23:43 +08:00
Kimdiego2098
7a1b4c6db7 update toucksocket 2023-10-18 21:51:49 +08:00
Kimdiego2098
f2c7664033 更新文档 2023-10-18 20:42:10 +08:00
Kimdiego2098
12b295f067 发布驱动包 2023-10-18 18:04:37 +08:00
Diego2098
3ee46da40f !12 【轻量级 PR】:修正心跳事件中的参数
Merge pull request !12 from youthalan/N/A
2023-10-18 06:59:17 +00:00
youthalan
beadaa7212 修正心跳事件中的参数
Signed-off-by: youthalan <youthalan@126.com>
2023-10-18 06:58:03 +00:00
Kimdiego2098
16ef09426f 更新版本号 2023-10-18 13:13:03 +08:00
Kimdiego2098
b8c1f1f5a9 初始化 采集/上传线程时 直接返回线程控制 2023-10-18 13:11:59 +08:00
Kimdiego2098
e027b5cbd6 更新OPCUAClient类库 2023-10-18 13:09:25 +08:00
Kimdiego2098
bf1bd73ad1 更新OPCUAClient类库 2023-10-18 12:45:26 +08:00
Kimdiego2098
86fc95119a 更新OPCUAClient类库 2023-10-18 12:44:58 +08:00
Kimdiego2098
b3a76ea17b 更新版本号与nuget发布 2023-10-18 12:39:42 +08:00
Kimdiego2098
bababd9d53 更新OPCUAClient类库 2023-10-18 12:38:20 +08:00
Diego2098
8cd5180531 !11 补充OPCClient类库事件的缺失文件
Merge pull request !11 from youthalan/N/A
2023-10-18 04:34:34 +00:00
youthalan
f1ccbade8c 补充OPCClient类库事件的缺失文件
Signed-off-by: youthalan <youthalan@126.com>
2023-10-18 04:32:39 +00:00
Diego2098
a66f4b0417 !10 添加连接或断开事件
Merge pull request !10 from youthalan/N/A
2023-10-18 04:20:22 +00:00
youthalan
ff495b2261 修改OPCUAClient类添加连接或断开事件,修改注入时不需要带参数
Signed-off-by: youthalan <youthalan@126.com>
2023-10-18 03:48:05 +00:00
Kimdiego2098
90e5824212 update 3.0.0.16 2023-10-18 00:38:36 +08:00
Kimdiego2098
3d64021062 opcuaClient浏览空间 添加是否显示子变量的选项 2023-10-18 00:36:44 +08:00
Kimdiego2098
fb8ed7428d 更改日志输出内容 2023-10-18 00:15:07 +08:00
Kimdiego2098
bdc273c10a update 3.0.0.15 2023-10-18 00:07:24 +08:00
Kimdiego2098
2d96cffdc7 增加不支持单文件发布的说明 2023-10-17 23:47:24 +08:00
Kimdiego2098
4356fccbcd update tdengineDB plugin 2023-10-17 23:43:48 +08:00
Kimdiego2098
a169fd4ce7 update touchsocket 2023-10-17 23:12:19 +08:00
Kimdiego2098
dcd418139e TD时序库插件,创建时间更改为主键 2023-10-17 23:08:09 +08:00
Kimdiego2098
0cac2062d3 update touchsocket 2023-10-17 23:06:21 +08:00
Kimdiego2098
854c5d4ade 更新nuget包 2023-10-17 21:00:50 +08:00
Kimdiego2098
0f96c6ec84 update SQLDB 2023-10-16 20:50:10 +08:00
Kimdiego2098
b219bd66c1 update touchsocket and other 2023-10-16 20:36:51 +08:00
Kimdiego2098
cb52f2c0b3 3.0.0.13 2023-10-16 17:44:09 +08:00
Kimdiego2098
d3273e03ef 增加关系库存储插件; 2023-10-16 17:40:17 +08:00
Kimdiego2098
afbfd963f3 增加时序库存储插件; 2023-10-16 17:40:13 +08:00
Kimdiego2098
2cd101c5f3 更新pro用户列表 2023-10-16 17:39:14 +08:00
Kimdiego2098
b10bc24dee 3.0.0.12 2023-10-16 08:47:39 +08:00
Kimdiego2098
51a23df861 opcuaclient添加是否加载服务端数据类型的选项 2023-10-16 08:46:46 +08:00
Kimdiego2098
03a0a9dad9 fix:tcpservice dispose err 2023-10-16 08:46:32 +08:00
Diego2098
362c0affbe !9 增加可选择安全订阅
Merge pull request !9 from youthalan/N/A
2023-10-16 00:33:19 +00:00
youthalan
d5b523479f 增加可选择安全订阅,以加快订阅速度
Signed-off-by: youthalan <youthalan@126.com>
2023-10-16 00:26:59 +00:00
Kimdiego2098
7719b8f6d7 更新3.0.0.11 2023-10-15 20:26:18 +08:00
Diego2098
3656c0d524 读取数据类型方法改为批量 2023-10-15 20:21:23 +08:00
Kimdiego2098
8b9e0dd6ea 同步Pro版本 2023-10-13 20:14:35 +08:00
Kimdiego2098
b3921b1037 update touchsocket 2023-10-13 19:16:12 +08:00
Kimdiego2098
d7d96e5dbf update nuget 2023-10-13 16:14:42 +08:00
Kimdiego2098
3934395b7b update 3.0.0.7 2023-10-13 11:54:27 +08:00
Kimdiego2098
04fbe9a529 添加三菱mc 3e帧二进制通讯协议文档 2023-10-13 11:51:51 +08:00
Kimdiego2098
badd6dd9a2 update demo 2023-10-12 18:52:44 +08:00
Kimdiego2098
059082456d 参数名称修改 2023-10-12 14:57:58 +08:00
Kimdiego2098
405b68e22f fix:mqtt保留消息未更新/更新错误 2023-10-12 14:57:36 +08:00
Kimdiego2098
c90bf6692c 3.0.0.6 2023-10-11 16:35:10 +08:00
Kimdiego2098
b5ea5d0cc5 update nuget 2023-10-11 16:28:18 +08:00
Kimdiego2098
243617a1e1 调整代码执行顺序 2023-10-11 16:19:34 +08:00
Kimdiego2098
9e703edd59 update ManageGateway 2023-10-11 10:59:33 +08:00
Kimdiego2098
d6c2cf2810 update ManageGateway 2023-10-11 10:41:09 +08:00
Kimdiego2098
ae7811acfa update 3.0.0.5 2023-10-10 21:09:34 +08:00
Kimdiego2098
e55731c099 fix:modbusRtu写入返回报文缓存逻辑修复 2023-10-10 21:08:42 +08:00
Kimdiego2098
9df5c74da4 update touchsocket 2023-10-10 20:03:40 +08:00
Kimdiego2098
8b75f9f785 update touchsocket 2023-10-10 20:02:15 +08:00
Kimdiego2098
bf12428cf4 fix:signalR razor dispose接口 2023-10-09 18:11:23 +08:00
Kimdiego2098
b551df978b update uaparser 2023-10-09 10:56:22 +08:00
Kimdiego2098
c91d85d2f0 添加MqttRpcDemo 2023-10-07 12:04:17 +08:00
Kimdiego2098
965237fa1f 添加清理日志任务配置参数 2023-10-06 18:28:06 +08:00
Kimdiego2098
24cd9afc06 update 3.0.0.2 2023-10-05 15:32:50 +08:00
Diego2098
602fdefebd update Directory.Build.props 2023-10-05 15:21:51 +08:00
Kimdiego2098
7e7818aa17 修复重启共享通道中的单个设备时,导致通道内其他设备变量异常 2023-10-05 00:34:12 +08:00
Kimdiego2098
27a6023a6a update opcuaclient 2023-10-04 17:12:47 +08:00
Kimdiego2098
3861879900 update opcuaclient 2023-10-04 16:59:15 +08:00
Kimdiego2098
807eeb8351 update nuget 2023-10-04 02:04:09 +08:00
Kimdiego2098
045fa53d58 update 3.0.0.1 2023-10-04 01:42:27 +08:00
Kimdiego2098
69d405ece7 实时报警列表线程同步 2023-10-04 01:27:03 +08:00
Kimdiego2098
d3dc4d0c5b modbusRtu 适配器 过滤干扰头部数据 2023-10-04 01:26:44 +08:00
Kimdiego2098
7beba1188e 更新种子数据 2023-10-04 01:24:34 +08:00
Kimdiego2098
9779ebe12c update windowsService bat 2023-10-03 19:11:42 +08:00
Kimdiego2098
f72cfaa093 update demo 2023-10-03 18:54:01 +08:00
Kimdiego2098
cf8ccafb4a rpc调用提示优化 2023-10-03 18:20:39 +08:00
Kimdiego2098
7fe281b0b8 fix:rpc 特殊方法分类错误 2023-10-03 17:05:49 +08:00
Kimdiego2098
5f70e9574f update demo 2023-10-02 22:37:50 +08:00
Kimdiego2098
4be51923ba update driverDebugPage 2023-10-02 18:34:55 +08:00
Kimdiego2098
a72c78f4a4 update opcuaClient 2023-10-02 18:30:58 +08:00
Diego2098
b4d32a6de1 add s7 wstring addressSign 2023-10-01 16:54:42 +08:00
Diego2098
8a6b2d5daa add s7 wstring addressSign 2023-10-01 16:49:07 +08:00
Diego2098
bebadeef14 更新demo 2023-10-01 13:33:25 +08:00
Kimdiego2098
3551be67f0 update demo csproj 2023-10-01 00:33:33 +08:00
Kimdiego2098
1103950b25 add EncodingMapper 2023-10-01 00:25:04 +08:00
Kimdiego2098
9d06e01cec 更新文档 2023-09-30 23:28:50 +08:00
Kimdiego2098
24be946b7e 默认不启用远程更新 2023-09-30 23:23:44 +08:00
Kimdiego2098
f56d2fc60f 更新readme 2023-09-30 23:09:48 +08:00
Kimdiego2098
9f8356f409 同步3.0.0版本代码 2023-09-30 23:05:53 +08:00
Kimdiego2098
8e6345d938 更新文档 2023-09-26 20:03:58 +08:00
Kimdiego2098
0eac78bd1c 2.1.0.15 2023-09-20 11:50:06 +08:00
Kimdiego2098
21db2502d0 2.1.0.15 2023-09-20 11:47:10 +08:00
Kimdiego2098
38c3492123 添加kafka/mq/iotsharp的间隔上传 2023-09-20 11:46:17 +08:00
Diego2098
6376bff476 fix:上传设备选择驱动时没有正确刷新 2023-09-18 00:10:02 +08:00
Kimdiego2098
38495a3ebc 2.1.0.14 2023-09-15 14:02:15 +08:00
Kimdiego2098
cf00897be2 ExpressionEvaluator改为多实例 2023-09-15 14:01:54 +08:00
Kimdiego2098
da640f24ec 2.1.0.13 2023-09-15 13:23:59 +08:00
Kimdiego2098
ed6a777c42 add dispose() 2023-09-15 13:23:39 +08:00
Kimdiego2098
81af4485d3 2.1.0.12 2023-09-15 13:20:29 +08:00
Kimdiego2098
94d8f0237d 更新多个依赖包版本 2023-09-15 13:19:04 +08:00
Kimdiego2098
c5e579dd38 修复因重复注册cancellationToken.Register导致的内存暴涨! 2023-09-15 13:17:36 +08:00
Kimdiego2098
7865e76ee2 CancellationToken代替CancellationTokenSource传入 2023-09-15 13:16:56 +08:00
Kimdiego2098
4d37212cc0 2.1.0.11 2023-09-11 09:35:24 +08:00
Kimdiego2098
9ca43f0bb4 feat:ManageGatewayWorker part 2023-09-11 09:31:26 +08:00
Kimdiego2098
fe8d262175 feat:ManageGatewayWorker part 2023-09-11 09:09:48 +08:00
Kimdiego2098
493d6e7165 feat:ManageGatewayWorker part 2023-09-11 09:09:03 +08:00
Kimdiego2098
17b630bcd8 TGAPPInfp更名APPInfo 2023-09-11 08:57:13 +08:00
Kimdiego2098
018c5ad3b4 Merge branch 'master' of https://gitee.com/dotnetchina/ThingsGateway 2023-09-11 08:54:01 +08:00
Kimdiego2098
a14b4da977 feat:ManageGatewayWorker part 2023-09-11 08:53:52 +08:00
Diego2098
01b4597cc7 !7 修复无法修改变量值问题
Merge pull request !7 from 如阳如木/master
2023-09-07 10:14:45 +00:00
如阳如木
0b083ccc1c 删除&& LastSetValue?.ToString() != data?.ToString()
Signed-off-by: 如阳如木 <970143933@qq.com>
2023-09-07 08:42:49 +00:00
Kimdiego2098
ca22cb0eae ManageGatewayConfig.json 2023-09-06 17:29:58 +08:00
Kimdiego2098
bcbb8e5f8c fix:_mqttServer null error 2023-09-06 17:27:36 +08:00
Kimdiego2098
bea286cdd4 2.1.0.10 2023-09-06 17:16:28 +08:00
Kimdiego2098
892d2ee8d2 从数据库取原属性 2023-09-06 17:14:24 +08:00
Kimdiego2098
7440608c14 更改变量 最近一次值 为 上次值 2023-09-06 16:44:58 +08:00
Kimdiego2098
6246f0295b 更改 变量最近一次值 为 上次值 2023-09-06 16:44:42 +08:00
Kimdiego2098
53c39d9b43 2.1.0.9 2023-09-06 16:30:50 +08:00
Kimdiego2098
9a50b60031 fix:串口断连/拔出/断电等情况,重新连接 2023-09-06 16:29:48 +08:00
Kimdiego2098
fd74527320 feat:ManageGatewayWorker part 2023-09-06 16:15:38 +08:00
Kimdiego2098
f91fb522ac feat:ManageGatewayWorker part 2023-09-06 16:10:29 +08:00
Kimdiego2098
7019c02b18 feat:ManageGatewayWorker part 2023-09-05 23:59:18 +08:00
Kimdiego2098
94f8422b9b feat:ManageGatewayWorker part 2023-09-05 23:37:02 +08:00
Kimdiego2098
b25c01567a feat:ManageGatewayWorker part 2023-09-05 23:33:02 +08:00
Kimdiego2098
b9329885de 2.1.0.8 2023-09-05 09:19:58 +08:00
Kimdiego2098
ac88c4cff9 fix:PeriodicTimer dispose 2023-09-05 09:17:26 +08:00
Kimdiego2098
9674f2d238 2.1.0.7 2023-09-05 09:06:39 +08:00
Kimdiego2098
38c9ecb6f1 fix:error!the stream dispose 2023-09-05 09:04:34 +08:00
Kimdiego2098
be9e9cdd5a Revert "fix:error!the stearm dispose"
This reverts commit a6d99fe227.
2023-09-05 09:03:49 +08:00
Kimdiego2098
afdaf07446 feat:mqttBroker part 2023-09-05 08:58:49 +08:00
Kimdiego2098
5caaf10225 fix:error!the stearm dispose 2023-09-05 08:57:42 +08:00
Diego2098
daffc3a776 feat:mqttBroker part 2023-09-04 22:34:31 +08:00
Diego2098
34c9748ce5 fix:uploadDeivce复制多个,ID重复问题 2023-09-04 22:11:50 +08:00
Diego2098
f17acbf4d1 刷新后选择行清空 2023-09-04 22:09:26 +08:00
Diego2098
229f56ce2f fix:采集设备线程初始化时更新活跃时间 2023-09-04 20:13:49 +08:00
Diego2098
0847bedc51 fix:特殊情况下无法获取程序集文件修改日期,所以去除 页脚-编译时间显示 2023-09-04 19:49:17 +08:00
Kimdiego2098
9e4546c305 fix:sqlsugar json支持类型改为bigstring 2023-09-04 17:09:38 +08:00
Kimdiego2098
97b01bf26a 更新文档 2023-09-04 15:03:03 +08:00
Kimdiego2098
e7011628d0 恢复Sqlite默认数据库 2023-09-02 19:17:32 +08:00
Kimdiego2098
044ffdecbd fix: page取消注入瞬时服务,改为App.GetService 2023-09-02 19:13:57 +08:00
Kimdiego2098
1a06a3543b feat:mqttBroker part 2023-09-02 13:55:21 +08:00
Kimdiego2098
5fedc0bf1e 更新文档 2023-08-31 09:17:59 +08:00
Kimdiego2098
7a881f867e 更新 变量管理-上传设备筛选功能 2023-08-31 08:58:30 +08:00
Kimdiego2098
dfe73a6cfb 2.1.0.6 2023-08-30 17:19:18 +08:00
Kimdiego2098
45d0fb27ad add windows service create/delete bat 2023-08-30 17:18:35 +08:00
Kimdiego2098
31890af61d update statusPage; 2023-08-30 14:12:50 +08:00
Kimdiego2098
a54b3ecc15 默认检测重连频率为10分钟 2023-08-29 17:56:59 +08:00
Kimdiego2098
c37cd1bd51 2.1.0.5 2023-08-29 17:44:28 +08:00
Kimdiego2098
0e3ee89cbf 添加报文日志入库选项 2023-08-29 17:42:56 +08:00
Kimdiego2098
a049176c14 添加 页脚 编译时间显示 2023-08-29 17:01:46 +08:00
Kimdiego2098
5ef2b0089e 调整导入excel错误提示 2023-08-29 16:45:54 +08:00
Kimdiego2098
1cc4899593 update opcdaclient null error 2023-08-29 15:24:29 +08:00
Kimdiego2098
26d1390be9 update GetBoolValue 2023-08-29 12:40:21 +08:00
Kimdiego2098
dc65584fb3 调整种子文件,增加Pro版本种子文件录入 2023-08-29 09:42:54 +08:00
Kimdiego2098
b3d5a708d5 更新2.1.0.4 2023-08-28 19:31:00 +08:00
Kimdiego2098
904a5d296b 修复串口基类 缓存包 失效错误;完善modbusrtu长度校验 2023-08-28 19:25:22 +08:00
Kimdiego2098
cc82e6a47a 更新touchsocket版本,更新2.1.0.3 2023-08-28 18:12:18 +08:00
Kimdiego2098
a9d02ec854 修复浏览大量节点空间时,BrowseNext方法参数releaseContinuationPoints改为false 2023-08-28 17:44:02 +08:00
Kimdiego2098
6b5a2d3767 修复调试界面-opcuaclient浏览节点展开逻辑错误 2023-08-28 10:51:35 +08:00
Kimdiego2098
10d9060b94 更新小版本2.1.0.2 2023-08-27 17:17:37 +08:00
Kimdiego2098
8441839c01 更新dlt645地址说明 2023-08-27 17:16:59 +08:00
Kimdiego2098
b669f5838f 添加日志查询 时间区间条件 2023-08-27 16:58:45 +08:00
Kimdiego2098
1a942f9ce1 2.1.0.1 2023-08-27 16:31:03 +08:00
Kimdiego2098
ae840758f7 dlt645添加数据标识校验 2023-08-27 16:30:32 +08:00
Kimdiego2098
082c08e5f9 更新dlt645,添加地址/控制码的校验规则 2023-08-27 16:15:51 +08:00
Kimdiego2098
74a43557ab 更新touchsocket 2023-08-27 15:59:57 +08:00
Kimdiego2098
184271789d 更新 Opc.Ua Version="1.4.372.56" 2023-08-27 14:33:24 +08:00
Kimdiego2098
fa61b0cad8 opcda/ua浏览地址空间 初始只加载首层节点 2023-08-27 14:14:47 +08:00
Kimdiego2098
7b197a90d1 opcuaclient浏览地址空间 初始只加载首层节点 2023-08-27 13:06:03 +08:00
Diego2098
62384af2f6 update MqttNetLogger 2023-08-26 21:38:14 +08:00
Diego2098
b466202a1a update MqttNetLogger 2023-08-26 20:40:23 +08:00
Diego2098
7ab38c18d8 添加在线/离线方法参数 2023-08-26 17:18:47 +08:00
Diego2098
39841ee43e 更新opcuaclient 2023-08-26 15:38:22 +08:00
Diego2098
e419800d98 更新opcuaclient,添加checkDomain属性,去除多余代码,更新SelectEndpoint方法 2023-08-26 15:33:03 +08:00
Kimdiego2098
63c99ab69a 更新文档 2023-08-25 19:49:02 +08:00
Kimdiego2098
e2a8fd2279 更新版本2.1.0 2023-08-25 19:45:28 +08:00
Kimdiego2098
bbde520471 更新解决方案 2023-08-25 19:38:47 +08:00
Kimdiego2098
45fb12f98e 更新DLT645文档 2023-08-25 19:33:33 +08:00
Kimdiego2098
4be3dcce50 添加DLT645_2007协议插件 2023-08-25 19:33:08 +08:00
Kimdiego2098
d12a41c769 调整Test位置 2023-08-25 19:32:27 +08:00
Kimdiego2098
ed9a58c9ed 调整非主从协议的状态判断策略;OPCUA去除主动连接 2023-08-25 17:23:00 +08:00
Kimdiego2098
4389cea5a1 crc校验优化 2023-08-22 17:01:10 +08:00
Kimdiego2098
d24854920b OPCUAClient添加是否使用SourceTime的选项 2023-08-22 16:15:34 +08:00
Kimdiego2098
9902443bee 更新文档 2023-08-22 12:21:45 +08:00
Kimdiego2098
8040b2ef16 2.0.9.3 2023-08-22 11:31:36 +08:00
Kimdiego2098
4533680b10 去除mqtt throw an _MqttConnectingFailedException_.报错信息 2023-08-22 11:26:22 +08:00
Kimdiego2098
3b28175135 更新批量写入方法,注意rpc入口参数变化,mqtt等RPC接口参数变化 2023-08-22 11:23:03 +08:00
Kimdiego2098
8e22812265 修正部分代码格式 2023-08-21 19:59:07 +08:00
Kimdiego2098
409bc91b9e 2.0.9.2 2023-08-19 12:08:49 +08:00
Kimdiego2098
61fe543a2a xml 2023-08-19 11:06:05 +08:00
Kimdiego2098
8a949a7e64 修复TcpClient重复释放导致锁异常 2023-08-19 11:05:29 +08:00
Kimdiego2098
b0dc9fb97a mas1.0.2默认中文 2023-08-18 18:36:30 +08:00
Kimdiego2098
83114d1002 更新masa1.0.2 2023-08-18 13:59:17 +08:00
Kimdiego2098
94a25a903f 2.0.9.1 2023-08-18 13:33:45 +08:00
Kimdiego2098
4f402f9e55 修改锁为EasyLock 2023-08-18 12:07:01 +08:00
Kimdiego2098
27bb2e3dcc 代码文件编码统一utf-8 2023-08-18 09:57:03 +08:00
Kimdiego2098
66cc52f6ec 更换DEMO Include为PackageReference 2023-08-17 15:30:35 +08:00
Kimdiego2098
ed1367b116 底层驱动添加netstandard输出 2023-08-17 15:24:07 +08:00
Kimdiego2098
5c055352e4 2.0.8 2023-08-17 10:58:48 +08:00
Kimdiego2098
8dcff5ada1 调整readme,方便copy账密 2023-08-17 10:28:47 +08:00
Kimdiego2098
4b8b23d7d5 优化变量地址输入分割方法;s7读写字符串添加默认编码 2023-08-17 09:50:10 +08:00
Kimdiego2098
e576f6aed3 优化s7读写字符串,更改特殊方法为读写共用 2023-08-16 17:20:20 +08:00
Kimdiego2098
97ab04da91 修复写入返回结果时的解析,0XFF为成功 2023-08-16 15:56:25 +08:00
Kimdiego2098
c6361bb36e 修复s7字符串打包读取 2023-08-16 15:47:37 +08:00
Kimdiego2098
b0b3dc225d 修复s7字符串读取 2023-08-16 15:35:15 +08:00
Kimdiego2098
9cbaba192a 主动释放锁 2023-08-16 14:56:47 +08:00
Kimdiego2098
f511598e13 OPCUA死区不再固定传入 2023-08-16 14:21:19 +08:00
Kimdiego2098
5ffff3b7aa fix: 优化JsonUtils中的获取type语句顺序 2023-08-16 08:59:18 +08:00
Kimdiego2098
24c4fa1c4f OPC系列增加导出excel/导入系统双选项 2023-08-15 17:38:40 +08:00
Kimdiego2098
7f312c1273 OPCUA采集设备写入字符串,不再需要传入双引号 2023-08-15 14:15:39 +08:00
Kimdiego2098
ef5c226c81 历史数据/报警 线程初始化时不再阻塞 2023-08-15 12:12:00 +08:00
Kimdiego2098
3f029cf799 更改ItemGroup Condition条件 2023-08-14 17:36:59 +08:00
Kimdiego2098
5bf64a8bec 传播token;
更新admin解决方案
2023-08-14 16:43:30 +08:00
Kimdiego2098
89f9c537f8 优化适配器解析代码 2023-08-14 14:21:00 +08:00
Kimdiego2098
e8d7f57818 2.0.6 2023-08-13 16:57:12 +08:00
Kimdiego2098
9b42fa78e7 OPCUACClient订阅初始化添加trycatch 2023-08-13 16:29:59 +08:00
Kimdiego2098
96d98e8f39 Merge branch 'master' of https://gitee.com/dotnetchina/ThingsGateway 2023-08-13 15:53:33 +08:00
Kimdiego2098
1f3a281e78 更新opcuaclient,初始读取server类型改为动态读取 2023-08-13 15:50:47 +08:00
Diego2098
dab4b66ee0 更新nuget包 2023-08-12 12:09:50 +08:00
Kimdiego2098
64dd192ddb 修复批量令牌强退失效 2023-08-11 15:15:50 +08:00
Kimdiego2098
b0a5c383d0 中间变量导入时添加标识 2023-08-11 14:15:22 +08:00
Kimdiego2098
4836cc99eb 2.0.5 2023-08-10 18:23:40 +08:00
Kimdiego2098
ba33873354 去除不需要的模板 2023-08-10 16:28:52 +08:00
Kimdiego2098
12c838cab0 修改详情模板 2023-08-10 11:23:18 +08:00
Kimdiego2098
a83a1c83a7 添加自定义详情模板 2023-08-10 10:35:19 +08:00
Kimdiego2098
2234f47170 底层修改:如果连接端口断开,停止等待返回 2023-08-10 10:35:01 +08:00
Kimdiego2098
4bae1b17fc 上传DTO添加 单位 参数 2023-08-10 09:09:09 +08:00
Kimdiego2098
6a7a0e0fa6 调整调试页面 2023-08-09 23:45:53 +08:00
Kimdiego2098
bff987c55f 添加单文件发布配置 2023-08-09 15:12:55 +08:00
Kimdiego2098
e467006913 去除页面多余引号 2023-08-09 13:45:27 +08:00
Kimdiego2098
1d646d4a6d 更新2.0.4 2023-08-09 13:00:10 +08:00
Kimdiego2098
70bd4936a1 更新opcua写入 2023-08-09 12:43:05 +08:00
Kimdiego2098
5ae7add6d2 更新opcua调试页面 2023-08-09 12:01:23 +08:00
Kimdiego2098
02b206ce7c 更新opcua 2023-08-09 11:47:04 +08:00
Kimdiego2098
2ea626a567 修复中间变量导入错误 2023-08-09 09:00:49 +08:00
Kimdiego2098
653909ec11 修正错误注释 2023-08-08 18:09:23 +08:00
Kimdiego2098
1d7c148eaf opcua添加初始化连接错误重试 2023-08-08 18:04:24 +08:00
Kimdiego2098
94a7e130ca 更新readme 2023-08-08 16:14:06 +08:00
Kimdiego2098
609abacdf0 优化大量设备线程重启的耗时 2023-08-08 14:02:00 +08:00
Kimdiego2098
033ff2e49a 2.0.3 2023-08-08 12:59:14 +08:00
Kimdiego2098
00a9bf0641 底层部分方法更改 2023-08-08 12:31:42 +08:00
Kimdiego2098
100f322456 报警/历史值查询时区转换 2023-08-08 09:05:15 +08:00
Kimdiego2098
2251e08d30 时间最小最大值时不再转换时区 2023-08-08 08:39:20 +08:00
Kimdiego2098
14e9beef53 Merge branch 'master' of https://gitee.com/dotnetchina/ThingsGateway 2023-08-08 08:28:37 +08:00
Kimdiego2098
c7cce23d54 默认不启用单用户登录 2023-08-08 08:28:27 +08:00
Diego2098
7becf32352 更新文档 2023-08-07 22:31:51 +08:00
Diego2098
741eda9f4e 去除DateTimeOffset类型 2023-08-07 22:30:38 +08:00
Kimdiego2098
473f51481c 更新文档 2023-08-07 17:38:53 +08:00
Kimdiego2098
684f9bd11b 更新readme 2023-08-07 17:34:32 +08:00
Kimdiego2098
b4b7f0c360 更新文件 2023-08-07 17:24:28 +08:00
Kimdiego2098
cf42d02628 更新文件 2023-08-07 17:16:53 +08:00
Kimdiego2098
cc9a9eab7b 更新文件 2023-08-07 16:56:45 +08:00
Kimdiego2098
784d6abc9b 更新文件 2023-08-07 16:33:36 +08:00
Kimdiego2098
0eafc07184 更新文件 2023-08-07 16:30:23 +08:00
Kimdiego2098
7e9499bd2e 更新文件 2023-08-07 15:46:30 +08:00
Kimdiego2098
d01a52aa95 更新文件 2023-08-07 15:36:49 +08:00
Kimdiego2098
6bcd492980 去除多余解决方案配置 2023-08-07 15:31:22 +08:00
Kimdiego2098
96b15da04b 添加发布脚本 2023-08-07 15:23:53 +08:00
Kimdiego2098
e0f1801693 更新readme 2023-08-07 15:18:42 +08:00
Kimdiego2098
d195ad85c5 更新readme 2023-08-07 15:13:21 +08:00
Kimdiego2098
773f7b6c1b 更新文档 2023-08-07 15:10:42 +08:00
Kimdiego2098
40f29fe399 2.0.0 2023-08-07 15:09:53 +08:00
Kimdiego2098
b19eb117c1 调试更新Blazor代码时不再跳转登录界面 2023-07-25 22:00:27 +08:00
Kimdiego2098
f930ba5eff 取消不必要的错误日志 2023-07-25 20:50:33 +08:00
Kimdiego2098
fbbda6c230 变量值更新错误提示; 2023-07-25 20:48:17 +08:00
Kimdiego2098
47db4968e5 优化大批量excel变量表导入效率 2023-07-25 18:26:48 +08:00
Kimdiego2098
1aff2e518c 删除不必要延时 2023-07-25 16:52:56 +08:00
Kimdiego2098
24134ca49e 发布1.7.6 2023-07-25 14:22:11 +08:00
Kimdiego2098
aadbe05a5e 优化导入excel效率 2023-07-25 14:21:17 +08:00
Kimdiego2098
18eddf4700 添加上传插件获取采集设备属性值的快捷方法 2023-07-23 13:44:37 +08:00
Kimdiego2098
0869ba7e70 Merge branch 'master' of https://gitee.com/dotnetchina/ThingsGateway 2023-07-23 09:59:11 +08:00
Kimdiego2098
cba5db1c53 线程完成时不再需要全局数据移除,有可能会导致偶发全局数据丢失 2023-07-23 09:58:43 +08:00
Kimdiego2098
0aa5430e23 线程完成时不要需要全局数据移除,有可能会导致偶发全局数据丢失 2023-07-23 09:57:03 +08:00
Kimdiego2098
88a91828cb 更新icon包版本 2023-07-23 00:03:46 +08:00
Kimdiego2098
27af89f5fe 更新icon包版本 2023-07-22 23:59:03 +08:00
Kimdiego2098
6f6d483bfe 修改插件日志为自定义日志 2023-07-22 23:34:09 +08:00
Kimdiego2098
915072c191 更新文档 2023-07-22 20:48:29 +08:00
Kimdiego2098
76077b92ae 发布1.7.4 2023-07-22 20:47:26 +08:00
Kimdiego2098
f0d1a527a5 修正错误名称 2023-07-22 20:32:34 +08:00
Kimdiego2098
fdb2a96d1c 添加上传插件最后错误原因 2023-07-22 20:10:45 +08:00
Kimdiego2098
849e3ac187 修复上传设备暂停按钮失效 2023-07-22 19:56:56 +08:00
Kimdiego2098
5c4cf21c59 OPCUAServer 数组维度不再写any;rpc写入添加延时 2023-07-22 17:02:44 +08:00
Kimdiego2098
40ee3e5d9b 更改假死检测间隔5分钟 2023-07-22 11:05:56 +08:00
Kimdiego2098
77a7cc034e 添加OPCUAServer线程间隔配置项,优化最后一次错误提示 2023-07-22 11:03:56 +08:00
Kimdiego2098
82b7881765 支持OPCUAServer数组类型,添加缓存文件存在判断 2023-07-21 21:28:39 +08:00
Kimdiego2098
3515775267 去除OPCUA写入数组维度校验 2023-07-20 17:37:49 +08:00
Kimdiego2098
10e12aa1c1 更新文档 2023-07-20 12:48:13 +08:00
Kimdiego2098
7fb4e62aa3 kafka尝试自动加载c库 2023-07-20 12:40:59 +08:00
Kimdiego2098
2605eaa614 修复中间变量Rpc写入 2023-07-20 10:55:37 +08:00
Kimdiego2098
8e97b4820d ModbusServer添加自定义循环间隔,修复中间变量写入 2023-07-20 10:55:17 +08:00
Kimdiego2098
89af4cdb68 更新文档 2023-07-19 18:02:31 +08:00
Kimdiego2098
6099c1a25a 更新文档 2023-07-19 15:43:28 +08:00
Kimdiego2098
fd23758aea 更改左侧菜单为手风琴效果 2023-07-19 15:00:40 +08:00
Kimdiego2098
8ad0b89db1 更新文档 2023-07-19 14:34:27 +08:00
Kimdiego2098
e7dffa3ff4 修改OPCUAClient心跳频率默认3s 2023-07-18 14:49:27 +08:00
Kimdiego2098
2772b5d2e2 更改OPCUAClient心跳频率为30000 2023-07-18 14:15:02 +08:00
Kimdiego2098
df6d9b5f70 更新1.7.3版本 2023-07-18 12:16:33 +08:00
Kimdiego2098
c9622d6d57 更新masa 稳定版以及其他包 2023-07-18 12:11:37 +08:00
Kimdiego2098
1f0b7c3c7e 修复添加订阅时,值死区过滤逻辑 2023-07-18 11:13:21 +08:00
Kimdiego2098
c12245037e 修复异步锁上下文切换导致OPCUA 心跳事件出错 2023-07-18 09:58:01 +08:00
Kimdiego2098
fe6d2a50a5 修复OPCUAClient调试界面重复输出订阅值 2023-07-18 08:51:27 +08:00
Kimdiego2098
7be84950f8 修改OPCUAClient的心跳频率配置项 2023-07-18 08:48:18 +08:00
Kimdiego2098
ba212da222 添加重启锁 2023-07-17 21:40:47 +08:00
2248356998 qq.com
122b833256 Merge branch 'master' of https://gitee.com/diego2098/thingsgateway-docs 2023-07-17 20:59:53 +08:00
2248356998 qq.com
7c73479041 更新opcua心跳状态日志 2023-07-17 20:59:32 +08:00
Diego2098
151f2e99ae 更新文档 2023-07-16 20:46:16 +08:00
Diego2098
da367e813f 更新授权名单 2023-07-16 20:31:56 +08:00
2248356998 qq.com
9143dfe336 更新文档 2023-07-16 18:27:22 +08:00
2248356998 qq.com
29914d6a72 更新文档 2023-07-16 18:12:55 +08:00
2248356998 qq.com
0dbef72a97 更新文档 2023-07-16 18:07:31 +08:00
2248356998 qq.com
3bb118bfe1 更新文档 2023-07-16 17:49:33 +08:00
2248356998 qq.com
85ce3be077 更新文档 2023-07-16 17:48:22 +08:00
2248356998 qq.com
064c2bf0a8 更新文档 2023-07-16 17:41:52 +08:00
2248356998 qq.com
c96d06ccc5 更新文档 2023-07-16 17:35:04 +08:00
2248356998 qq.com
a658dbb753 更新文档地址 2023-07-16 17:32:49 +08:00
2248356998 qq.com
bf5d5b629e 迁移文档 2023-07-16 17:28:26 +08:00
2248356998 qq.com
241d576519 冗余设备删除后会导致后台出错 2023-07-16 11:36:45 +08:00
2248356998 qq.com
7807e9bdd0 更新readme 2023-07-16 10:28:43 +08:00
2248356998 qq.com
fcbf15fed6 删除无用属性 2023-07-16 09:40:00 +08:00
2248356998 qq.com
cd90a11209 修复1.7.0版本修改导致的mqttrpc映射错误 2023-07-15 22:56:28 +08:00
2248356998 qq.com
ff65707ea2 增加 上传插件的列表分割大小,因为某些情况下传输字节太大会导致失败 2023-07-15 22:42:34 +08:00
2248356998 qq.com
b16026070c 导入提示的当前行显示未初始 2023-07-15 22:35:12 +08:00
2248356998 qq.com
1b5fbf86d8 格式化整理 2023-07-15 22:32:54 +08:00
2248356998 qq.com
5eb1be8101 插件报文截取前200字符,防止页面渲染过多 2023-07-15 20:04:14 +08:00
2248356998 qq.com
6a863cd26a kafka插件增加超时选项 2023-07-15 17:27:35 +08:00
2248356998 qq.com
87ebb7b6c7 历史服务修复变量在线状态显示错误 2023-07-15 15:46:39 +08:00
2248356998 qq.com
1233a74307 发布1.7.2版本 2023-07-15 11:10:29 +08:00
2248356998 qq.com
221890acfb 优化OPCUA错误提示 2023-07-14 17:33:11 +08:00
2248356998 qq.com
687c7e766e OPCUA在取消订阅时应该走读取方法 2023-07-14 17:22:19 +08:00
2248356998 qq.com
ba22c407d3 优化导入excel提示 2023-07-14 16:23:42 +08:00
2248356998 qq.com
a8b9ed56b5 优化导入excel提示 2023-07-14 16:23:35 +08:00
2248356998 qq.com
3004869e19 kafka 插件释放时取消事件注册 2023-07-14 14:35:40 +08:00
2248356998 qq.com
1145853fe1 更改属性说明 2023-07-14 12:23:46 +08:00
2248356998 qq.com
f1617a25b1 添加kafka插件null传播 2023-07-14 10:41:04 +08:00
2248356998 qq.com
932be558d9 opcda JValue转object 2023-07-14 10:14:05 +08:00
2248356998 qq.com
ee3a37d3b9 opcua读取值JValue转为object 2023-07-14 10:04:05 +08:00
2248356998 qq.com
5ac412a582 RabbitMQ,IotSharp添加离线缓存 2023-07-13 19:54:12 +08:00
2248356998 qq.com
1625290bc3 Variable Value数据类型改为object 2023-07-13 18:18:53 +08:00
2248356998 qq.com
1ec169ad49 中间变量页种子ID重复 2023-07-13 17:47:22 +08:00
2248356998 qq.com
53be552e44 复制设备没有及时刷新缓存 2023-07-13 17:38:45 +08:00
2248356998 qq.com
d3a75d46b9 发布1.7.1 2023-07-13 11:19:17 +08:00
2248356998 qq.com
256512c961 代码格式化 2023-07-13 11:18:36 +08:00
2248356998 qq.com
5c12ac5bcc OPCDA增加数组支持,增加写入动态类型支持 2023-07-13 11:16:31 +08:00
2248356998 qq.com
02a2bcb113 优化OPCUA数组类型转换 2023-07-13 09:02:36 +08:00
2248356998 qq.com
1f0a01c725 修复blazor界面null错误 2023-07-12 22:12:11 +08:00
2248356998 qq.com
6ea164ede1 更新ReadMe 2023-07-12 21:33:05 +08:00
2248356998 qq.com
65bae85ecc 删除重复文件 2023-07-12 21:30:41 +08:00
2248356998 qq.com
2fd7dcf4d0 更新nuget包 2023-07-12 21:21:50 +08:00
2248356998 qq.com
aa3bbbe038 V1.7.0发布
1、增加采集通道冗余
2、优化多个界面
3、导出导入功能优化
4、增加中间变量
5、OPCUAClient支持动态类型
6、离线缓存多处覆盖,包含上传插件/历史报警/时序库
7、增加通用调试界面,增加s7调试
8、其他优化
2023-07-12 21:16:38 +08:00
2248356998 qq.com
6cb09a6f95 修复AppDataTable清空MForm模型导致筛选列清空的问题 2023-07-10 13:18:14 +08:00
2248356998 qq.com
45868f05d3 修复写入s7协议 bit值 偏移错误(以byte数据块为准) 2023-07-08 17:01:44 +08:00
2248356998 qq.com
aac7401e20 修复读取s7协议 bit值 偏移错误(以byte数据块为准) 2023-07-08 11:44:57 +08:00
2248356998 qq.com
c2fc290edd 默认添加kafka插件 种子数据 2023-07-07 17:13:00 +08:00
2248356998 qq.com
330357fc36 修复布尔量解析时反转字节导致结果值不符的错误 2023-07-06 14:05:09 +08:00
2248356998 qq.com
f4a3d6a64e null传播 2023-07-03 14:13:00 +08:00
2248356998 qq.com
2e0963ec81 添加x86架构 2023-07-03 10:48:52 +08:00
2248356998 qq.com
897cb6e62a 补充插件实例内容 2023-07-02 19:50:09 +08:00
2248356998 qq.com
80868bd48e blazor组件初始化并行执行,导致sugar单例DB出现线程偶发错误,暂增加copyNew()解决 2023-06-29 16:42:19 +08:00
2248356998 qq.com
b3df78c56f blazor组件初始化并行执行,导致sugar单例DB出现线程偶发错误,暂增加copyNew()解决 2023-06-29 16:36:03 +08:00
2248356998 qq.com
783a4259e3 sqlsugar偶发线程故障,添加copyNew方法 2023-06-28 18:54:45 +08:00
2248356998 qq.com
fcf19b8dc8 登录跳转uri优化 2023-06-28 15:47:35 +08:00
2248356998 qq.com
1f9f89817d 更新开源说明 2023-06-28 10:48:33 +08:00
2248356998 qq.com
7b94da7d85 update openapiUser datatableUI 2023-06-26 19:51:02 +08:00
2248356998 qq.com
164406f6c2 css脚本结果不采用 System.Text.Json.JsonSerializer 2023-06-26 19:44:17 +08:00
2248356998 qq.com
90ef2adc6b OPCUAClient修复当变量为string类型时的数值过滤失败导致添加订阅失效 2023-06-26 19:43:00 +08:00
2248356998 qq.com
d65f10f88b 停用采集设备时,变量获取运行态出错 2023-06-26 16:43:48 +08:00
2248356998 qq.com
dca0ece9e0 登录后跳转原url 2023-06-26 14:46:24 +08:00
2248356998 qq.com
baabc155c8 cookie授权验证失败时返回登录界面 2023-06-26 14:36:41 +08:00
Diego2098
7eb94412d6 更新nuget类库 2023-06-25 18:59:09 +08:00
2248356998 qq.com
0fc8b24f85 添加报警事件None枚举 2023-06-20 13:10:58 +08:00
2248356998 qq.com
88f6ef5b96 修复cpu核心等于1时报错,修正登录错误提示 2023-06-20 09:09:08 +08:00
Diego2098
7442483419 修复modbusServer 初始化错误 2023-06-17 18:14:13 +08:00
2248356998 qq.com
9c61933c04 sugar添加取消令箭 2023-06-15 17:38:36 +08:00
2248356998 qq.com
2b36a99720 增加变量运行态 CollectVariableRuntime 所在设备属性 2023-06-15 16:50:08 +08:00
2248356998 qq.com
c2cfc42ba4 parallel.foreach无序体验不好,退回为foreach 2023-06-14 11:04:09 +08:00
2248356998 qq.com
4f32704e08 update 1.6.1 version 2023-06-13 22:39:47 +08:00
2248356998 qq.com
fccf43685f update nuget package 2023-06-13 22:39:25 +08:00
2248356998 qq.com
39135d81ad update driver messagesui 2023-06-13 22:26:19 +08:00
2248356998 qq.com
ff4b10681e 并行关闭线程 2023-06-12 14:41:08 +08:00
2248356998 qq.com
10b7908fc2 添加mqtt/kafka上传内容显示 2023-06-12 14:40:53 +08:00
2248356998 qq.com
31790da8c6 优化大量变量excel上传的验证过程 2023-06-12 11:35:27 +08:00
2248356998 qq.com
4621201c47 更新文档站点地址 2023-06-11 18:37:50 +08:00
2248356998 qq.com
692ac31dbf 删除docs 2023-06-11 18:22:15 +08:00
2248356998 qq.com
ed1d163d55 更新1.6.0版本 2023-06-11 17:58:57 +08:00
2248356998 qq.com
e931c9040c 缓存最大默认2000 2023-06-11 17:56:34 +08:00
2248356998 qq.com
f23cb48ea4 添加离线缓存大小限制配置 2023-06-11 17:55:03 +08:00
2248356998 qq.com
c3b2b6b07b 上传插件线程等待时间改为10ms 2023-06-11 17:47:16 +08:00
2248356998 qq.com
f89bf590ba 导入变量优化 2023-06-11 17:46:23 +08:00
2248356998 qq.com
fea17dc00b 添加mqttClient离线缓存 2023-06-11 17:45:46 +08:00
2248356998 qq.com
dbb1069920 添加kafka离线缓存 2023-06-11 17:45:27 +08:00
Diego2098
afc4ccfaa9 !6 部分配置会导致SqlServer自动建库失败
Merge pull request !6 from samisgod/master
2023-06-10 06:55:34 +00:00
samisgod
38d55a1c07 fix db init for SqlServer 2023-06-10 14:51:29 +08:00
2248356998 qq.com
5fca29f103 增加pwa 2023-06-09 17:53:41 +08:00
2248356998 qq.com
78f34b2ca4 修复停用验证码时登录失败显示null报错 2023-06-09 17:09:17 +08:00
2248356998 qq.com
8e8028e809 统一文件编码 2023-06-09 15:04:54 +08:00
2248356998 qq.com
8dc70687f8 update solution folder 2023-06-09 14:30:53 +08:00
2248356998 qq.com
fa22f9ee64 upload deviceStatusPage 2023-06-09 10:32:11 +08:00
2248356998 qq.com
b33c0d3f81 update deviceStatusPage 2023-06-09 09:49:03 +08:00
2248356998 qq.com
339009add4 remove dotNET China declaration 2023-06-09 09:10:30 +08:00
2248356998 qq.com
798c823c4b add dotNET China declaration 2023-06-09 09:02:17 +08:00
Diego2098
14ad86f2a3 重启线程时增加运行状态界面空传播防止报错 2023-06-08 21:35:07 +08:00
2248356998 qq.com
a3a714dc17 add upload plugin code description 2023-06-08 17:47:53 +08:00
2248356998 qq.com
e8d8b0d41d 迁移导入变量功能到驱动调试内 2023-06-08 15:11:58 +08:00
2248356998 qq.com
17daad8f89 plugin unload test,but failed 2023-06-08 10:52:39 +08:00
2248356998 qq.com
e178263b3b 更改默认api文档为Knife4j 2023-06-07 20:34:31 +08:00
2248356998 qq.com
4febbc261e 缓存键增加Type-TypeHandle句柄 2023-06-07 18:42:58 +08:00
2248356998 qq.com
ef3a6942fd 修改OPCUA证书路径,增加默认接收不收信任证书与其选项;
OPCDA整理;
2023-06-07 18:03:57 +08:00
2248356998 qq.com
f8ac2be62b 重启线程WebApi修改 2023-06-07 11:32:18 +08:00
2248356998 qq.com
25447c34e5 添加注释提示 2023-06-07 11:28:11 +08:00
2248356998 qq.com
37cdf8fd48 GC策略更改,大量变量实例手动清空以快速内存释放 2023-06-07 11:19:24 +08:00
2248356998 qq.com
ac8e7dc959 上传1.5.1 2023-06-06 19:37:04 +08:00
2248356998 qq.com
8293382840 Merge branch 'master' of https://gitee.com/diego2098/ThingsGateway 2023-06-05 08:33:50 +08:00
Diego2098
d85a3e7743 S7-TCP连接修复死锁 2023-06-02 22:20:21 +08:00
2248356998 qq.com
f3f5ffb5c8 增加KINGVIEW 配置 2023-05-29 01:32:37 +08:00
2248356998 qq.com
99e6702c62 超管用户名称不可更改 2023-05-26 00:14:05 +08:00
2248356998 qq.com
e143c25078 初步添加OPCUAClient调试界面;更新依赖 2023-05-25 23:41:11 +08:00
2248356998 qq.com
3fb67972be OPCUA安全策略添加全部选项 2023-05-25 00:59:21 +08:00
2248356998 qq.com
61c04d4e09 OPCUA安全策略添加全部选项 2023-05-25 00:58:47 +08:00
2248356998 qq.com
b22f728958 添加OPCDAClient调试页面 2023-05-25 00:11:00 +08:00
2248356998 qq.com
112be7e383 修改程序根目录为文件所在目录 2023-05-24 14:37:45 +08:00
2248356998 qq.com
6a7f83fed5 默认添加服务守护支持 2023-05-24 14:32:21 +08:00
2248356998 qq.com
4b9698a735 ModbusServer添加自定义数据类型;修复发布时静态文件路径错误 2023-05-24 12:31:20 +08:00
2248356998 qq.com
ec3d203a6d Add copyright notices 2023-05-23 23:54:28 +08:00
2248356998 qq.com
e77b8f5475 update TGTcpClient 2023-05-23 22:42:48 +08:00
2248356998 qq.com
da8bbd321f 添加Modbus系列插件调试页面;添加Modbus组包解析缓存超时时间; 2023-05-23 20:50:44 +08:00
2248356998 qq.com
98fd011def update HardwareInfoService 2023-05-23 12:04:04 +08:00
2248356998 qq.com
9dccbc5316 nuget更新 2023-05-23 11:59:28 +08:00
2248356998 qq.com
03fa26daf9 硬件界面添加限值防报错 2023-05-23 11:46:19 +08:00
2248356998 qq.com
fbc41e3895 初步添加插件驱动调试页面 2023-05-22 18:50:30 +08:00
2248356998 qq.com
f1a6d0c02c 初步添加插件驱动调试页面 2023-05-22 18:41:09 +08:00
2248356998 qq.com
67f6bb7155 Razor文件格式化清理 2023-05-22 18:40:50 +08:00
2248356998 qq.com
86282f596c 删除调试代码... 2023-05-22 17:09:21 +08:00
2248356998 qq.com
e0f24c795c 更改变量读取间隔限制最低为10ms 2023-05-22 15:17:22 +08:00
2248356998 qq.com
7d44ed860c 明确System.Management版本 2023-05-22 14:51:02 +08:00
2248356998 qq.com
091094a24c 硬件信息获取添加异常拦截 2023-05-22 14:33:30 +08:00
2248356998 qq.com
ba18ee518c update adapter 2023-05-22 12:42:31 +08:00
2248356998 qq.com
513a031691 代码清理 2023-05-21 22:39:33 +08:00
2248356998 qq.com
cc79de1106 优化opcua质量戳提示;添加数据转换基础方法;TCP等待返回时默认断开连接立即返回 2023-05-21 10:51:56 +08:00
2248356998 qq.com
bd90e8efb2 modbus 组包优化 2023-05-20 21:41:16 +08:00
2248356998 qq.com
4bd88ff11d 类型更换 2023-05-20 17:13:14 +08:00
2248356998 qq.com
d27ee61292 可指定OPCUA节点数据类型 2023-05-20 15:40:17 +08:00
2248356998 qq.com
0c20b3345f 更新文档 2023-05-20 13:52:23 +08:00
2248356998 qq.com
0f3c8c7193 添加自定义OPCUAServer数据类型 2023-05-20 12:34:06 +08:00
2248356998 qq.com
16761ec605 导入规则优化,主动抛出名称重复错误 2023-05-19 22:55:53 +08:00
2248356998 qq.com
1069440cda 更改插件时开启刷新设备属性 2023-05-19 22:26:37 +08:00
2248356998 qq.com
60f5702f17 判断OPCUAServer状态时增加tryCatch 2023-05-19 21:27:20 +08:00
2248356998 qq.com
ee70133e47 判断OPCUAServer状态时增加tryCatch 2023-05-19 21:24:56 +08:00
2248356998 qq.com
06a9fdeb2e 优化tcp拆包组包 2023-05-19 20:28:51 +08:00
2248356998 qq.com
f14bd62004 快捷方式null错误 2023-05-19 16:33:57 +08:00
2248356998 qq.com
67cbaf22b7 更新依赖包 2023-05-19 16:23:21 +08:00
2248356998 qq.com
60b166dba2 UDP通讯优化 2023-05-19 16:21:42 +08:00
2248356998 qq.com
66e1647ede 添加链路锁 2023-05-19 14:27:47 +08:00
2248356998 qq.com
a1a35c00a5 分包数量显示错误 2023-05-19 10:13:48 +08:00
2248356998 qq.com
b02e3361c4 Modbus读写锁更新 2023-05-19 10:05:54 +08:00
2248356998 qq.com
2b9ceaa25a Modbus读写锁更新 2023-05-19 09:53:56 +08:00
2248356998 qq.com
109b5a9755 删除sqlsugar旧版本代码,会导致sqlite不兼容 2023-05-19 09:15:32 +08:00
2248356998 qq.com
cc4df86c10 适配mysql;修复写入表达式转换;优化web页面写入体验 2023-05-18 23:38:40 +08:00
2248356998 qq.com
e93532c395 数据库初始化修复 2023-05-18 22:24:27 +08:00
2248356998 qq.com
6ff688326a 数据库连接自动释放 2023-05-18 21:06:06 +08:00
2248356998 qq.com
eda5d2872f 编辑页面添加 变量 允许远程写入选项 2023-05-18 21:03:44 +08:00
2248356998 qq.com
dc88394f5f 修复枚举类型在mysql中类型出错的问题 2023-05-18 20:58:14 +08:00
Diego2098
ec85c9a2c6 修改文件大小限制 2023-05-17 22:29:59 +08:00
Diego2098
1341638556 更新设备读写锁 2023-05-17 22:05:31 +08:00
2248356998 qq.com
4875dfee11 touchSocket修复UDP在windows下重置连接的问题;更新nuget 2023-05-17 16:54:33 +08:00
2248356998 qq.com
d834ba8bd4 代码格式清理 2023-05-17 16:49:25 +08:00
2248356998 qq.com
6191067771 修复CancellationTokenSource未释放导致Linux下偶发内存问题 2023-05-17 16:44:52 +08:00
2248356998 qq.com
6c3a571163 OPCUAServer节点数据类型增加在读取表达式转换后的判断 2023-05-16 09:05:58 +08:00
Diego2098
5efb07e10e ModbusClient添加帧前时间 2023-05-15 22:45:55 +08:00
2248356998 qq.com
b25ec18ce6 修复using作用域导致获取的服务可能被释放的问题 2023-05-15 19:21:35 +08:00
2248356998 qq.com
f3cd281241 nuget更新 2023-05-15 18:55:27 +08:00
2248356998 qq.com
58b93cbf4c mqttClient同步间隔上传的实体类 2023-05-12 18:22:06 +08:00
2248356998 qq.com
57fc0349ff update mqttClient 2023-05-12 16:40:55 +08:00
2248356998 qq.com
d0a2cea772 MqttClient增加间隔上传选项;更改线程循环间隔说明定义 2023-05-12 16:31:57 +08:00
Diego2098
58d5801fb5 update readme 2023-05-10 21:18:43 +08:00
Diego2098
e5f2e59798 增加api控制采集启停等方法 2023-05-10 21:17:34 +08:00
Diego2098
1166921057 提交kafka插件 2023-05-10 21:17:09 +08:00
2248356998 qq.com
aeb49576f2 update readme 2023-05-10 17:49:52 +08:00
2248356998 qq.com
7ef1fecef8 共享链路写入变量修复;Nuget更新 2023-05-10 16:33:59 +08:00
2248356998 qq.com
fd630373b5 更新文档 2023-05-09 17:51:55 +08:00
2248356998 qq.com
d365d8f170 更新文档 2023-05-09 17:47:22 +08:00
2248356998 qq.com
dfa5e1172f OPCUAClient/Server 修改证书有效期为100年 2023-05-09 14:37:18 +08:00
2248356998 qq.com
daf195898a 添加控制台logo 2023-05-08 18:10:07 +08:00
2248356998 qq.com
c61c0a39cf Code Cleanup 2023-05-08 18:03:32 +08:00
2248356998 qq.com
6137e6baa5 add GetSciptValue 2023-05-08 17:45:13 +08:00
2248356998 qq.com
fd16fd9ffe modbus rtu 粘包优化 2023-05-08 15:59:35 +08:00
2248356998 qq.com
2b8bd5f2cc modbus rtu报文粘包优化;共享链路切换延时;上传插件添加报文界面 2023-05-08 15:57:33 +08:00
2248356998 qq.com
d312c2e9e7 plc read with CancellationToken 2023-05-08 13:33:01 +08:00
2248356998 qq.com
578b0d2268 粘包优化 2023-05-08 10:55:16 +08:00
2248356998 qq.com
ffb41b0109 Rpc条件bug修复 2023-05-07 18:06:27 +08:00
2248356998 qq.com
c63fb5d796 OPCUAServer修复匿名登录 2023-05-07 18:06:09 +08:00
2248356998 qq.com
8caee732e8 更新文档 2023-05-07 16:56:41 +08:00
2248356998 qq.com
df5381adce 属性顺序调整 2023-05-05 09:46:11 +08:00
2248356998 qq.com
f34836b7fa 更新文档 2023-05-04 22:58:36 +08:00
2248356998 qq.com
44b459883a update CollectDeviceThread 2023-05-04 10:58:45 +08:00
2248356998 qq.com
14a8592ae3 更新文档 2023-05-02 22:11:55 +08:00
2248356998 qq.com
787d0dce4a 更新1.5.0 2023-05-02 22:06:09 +08:00
2248356998 qq.com
8be05ff93d 1、共享链路支持;
2、设备报文查看;
3、采集线程重构;
2023-05-02 21:58:11 +08:00
2248356998 qq.com
3014af565c mqtt重连锁优化 2023-04-27 11:19:32 +08:00
2248356998 qq.com
315252bdc4 添加常用转换 2023-04-24 17:54:00 +08:00
2248356998 qq.com
c36c3b4607 masa更新 2023-04-24 11:16:28 +08:00
2248356998 qq.com
7d5e939bab 更新包 2023-04-24 09:26:47 +08:00
Diego2098
f75c9d1eed 报警后台服务去除多余接口
Signed-off-by: Diego2098 <2248356998@qq.com>
2023-04-19 00:48:13 +00:00
2248356998 qq.com
38cb9855ea 添加docker文件 2023-04-17 17:39:12 +08:00
2248356998 qq.com
ed8b56d624 单文件发布 2023-04-17 15:13:40 +08:00
2248356998 qq.com
bca48b13ae update SqlSugarConfig 2023-04-17 11:58:41 +08:00
2248356998 qq.com
29426edb05 删除其他信息 2023-04-17 11:05:38 +08:00
2248356998 qq.com
33a2dc687f 验证码更新修复 2023-04-17 09:05:44 +08:00
2248356998 qq.com
e2398a21b2 1、字段null约束修改
2、ModbusServer绑定端口优化
2023-04-16 15:02:09 +08:00
2248356998 qq.com
f19530276e 更新readme 2023-04-16 13:34:29 +08:00
2248356998 qq.com
2b9b92a78c 更新1.4.0
注意Excel导入已不适用以前版本
1、去除动态更新插件
2、改用MiniExcel,支持动态导入,excel配置更简单
3、优化多处界面与后台逻辑,部分方法规范更名
4、修复外网地址获取错误等
2023-04-15 20:48:56 +08:00
2248356998 qq.com
99c55dac10 修复报警文本逻辑 2023-04-11 13:40:13 +08:00
2248356998 qq.com
25667e46f9 1,修复控制台报错(echarts.js问题,已删除)2,多处Dispose修正 2023-04-07 08:48:34 +08:00
2248356998 qq.com
2f4e8f2399 过滤 2023-04-06 14:46:24 +08:00
2248356998 qq.com
9169183769 历史数据库选择为sqlite时查询转换日期错误 2023-04-05 18:04:33 +08:00
2248356998 qq.com
c420f50831 启用开发环境web详细日志,调整App.Razor位置,添加网页ico 2023-04-05 17:14:26 +08:00
2248356998 qq.com
c6c9279ef4 update console/file datetime format 2023-04-05 15:49:00 +08:00
2248356998 qq.com
c125e2991d 更新readme 2023-04-04 18:41:56 +08:00
2248356998 qq.com
5348b19d6a 😀版本1.3.1 2023-04-04 17:55:52 +08:00
2248356998 qq.com
afd426daac 网关软件时间统一UTC 2023-04-04 17:55:15 +08:00
2248356998 qq.com
202c511cfa update iotSharpClient 2023-04-04 11:07:05 +08:00
2248356998 qq.com
651eb295a4 😀 更新1.3.0 2023-04-04 10:15:34 +08:00
2248356998 qq.com
d798aaed33 update iotSharpClient 2023-04-04 09:25:16 +08:00
2248356998 qq.com
63ef347cc9 update iotSharpClient 2023-04-04 09:23:22 +08:00
2248356998 qq.com
3139b2d5a0 IosSharpClient Rpc优化 2023-04-04 09:22:22 +08:00
2248356998 qq.com
2c80bbb244 IotSharp Rpc方法完善 2023-04-04 09:19:48 +08:00
2248356998 qq.com
af06755ada 添加写入多个变量的api方法 2023-04-04 09:18:58 +08:00
2248356998 qq.com
97d6dbaa6c update IotSharpClient 2023-04-03 20:16:38 +08:00
2248356998 qq.com
74cf82a1c7 更新种子 2023-04-03 19:34:12 +08:00
2248356998 qq.com
74634889ab 添加IotSharp插件 2023-04-03 19:30:52 +08:00
2248356998 qq.com
dbe30fcd77 分页显示令牌 2023-04-03 15:29:35 +08:00
2248356998 qq.com
24a7f3a320 后台启动时Furion RootServices NULL值 2023-04-03 15:19:59 +08:00
2248356998 qq.com
d3650f1145 修复不存在采集设备时,初始化报警/历史服务bug 2023-04-03 14:12:56 +08:00
2248356998 qq.com
4801db9050 弹窗消息在SignalR订阅方法中需InvokeAsync 2023-04-03 13:35:14 +08:00
2248356998 qq.com
6445281658 修复规范化结果包装2次导致登录返回结果不正确的问题 2023-04-03 12:45:19 +08:00
2248356998 qq.com
87719f5938 更新演示地址 2023-04-03 10:47:49 +08:00
2248356998 qq.com
5ba1ec433b readme 2023-04-02 18:12:18 +08:00
2248356998 qq.com
7c5b382458 readme 2023-04-02 18:08:56 +08:00
2248356998 qq.com
8960426128 更新readme 2023-04-02 18:05:54 +08:00
2248356998 qq.com
e1d47d5a92 更新readme 2023-04-02 18:05:28 +08:00
2248356998 qq.com
9407c272aa 更新包 2023-04-02 17:10:32 +08:00
2248356998 qq.com
bccbd7b400 添加注释 2023-04-02 16:59:46 +08:00
2248356998 qq.com
a24dc010ec 调整依赖,添加关系图 2023-04-01 17:28:35 +08:00
2248356998 qq.com
c5e5d50fb8 调整依赖,更新版本1.2.1 2023-04-01 15:45:02 +08:00
2248356998 qq.com
be1c520320 整理 2023-04-01 13:57:57 +08:00
2248356998 qq.com
3d18d0f893 😀 OPCUAServer支持历史查询数据 2023-03-31 18:32:55 +08:00
2248356998 qq.com
c3351a38a6 添加ThingsGateway.Foundation注释 2023-03-31 16:25:33 +08:00
2248356998 qq.com
0b86340a8d 硬件信息获取添加延时 2023-03-30 20:51:08 +08:00
2248356998 qq.com
829371b032 脚本显示优化 2023-03-30 19:51:38 +08:00
2248356998 qq.com
b342207bc7 更新文档 2023-03-30 19:39:35 +08:00
2248356998 qq.com
58f4fdced3 mqtt/mq上传 添加上传实体自定义脚本 2023-03-30 19:07:18 +08:00
2248356998 qq.com
253844cbcf 表达式整理 2023-03-30 16:23:03 +08:00
2248356998 qq.com
e2e2b9ffb4 更新版本 2023-03-30 14:12:39 +08:00
2248356998 qq.com
2568042f5f 更新文档 2023-03-30 14:11:16 +08:00
2248356998 qq.com
722edf4b9a readme 2023-03-30 14:06:03 +08:00
2248356998 qq.com
e406d364e4 更新readme,nuget 2023-03-30 14:05:00 +08:00
2248356998 qq.com
31c37f41b2 删除多余代码 2023-03-30 13:53:57 +08:00
2248356998 qq.com
6cd879bbc5 opcua 写入添加用户名日志 2023-03-30 13:32:32 +08:00
2248356998 qq.com
b7974050fe 去除ua数据类型验证 2023-03-30 13:20:26 +08:00
2248356998 qq.com
e9c9d0816e 更新readme 2023-03-30 13:13:44 +08:00
2248356998 qq.com
a437692e1a 类命名错误更改 2023-03-30 13:11:46 +08:00
2248356998 qq.com
2a14d2f3c8 发布文件添加 2023-03-30 13:11:43 +08:00
2248356998 qq.com
559e2d1889 添加OPCUAServer插件,修复个别bug 2023-03-30 13:10:31 +08:00
Diego2098
a1aa919f80 1、OPCUAClient修复客户端证书未自动生成
2、修复异步方法错误使用
2023-03-30 00:15:13 +08:00
2248356998 qq.com
cbccb27a5a 添加部分代码注释 2023-03-29 16:22:01 +08:00
2248356998 qq.com
bbad36d576 驱动支持主机名称 2023-03-29 11:07:21 +08:00
2248356998 qq.com
df6c9d55b5 修复初始化失败导致的一系列问题 2023-03-29 10:53:42 +08:00
2248356998 qq.com
36a1d9c364 上传插件间隔时间修正 2023-03-28 16:40:00 +08:00
2248356998 qq.com
1f3681d5ac 历史变量enable失效 2023-03-28 16:38:29 +08:00
2248356998 qq.com
0159f8e53f 历史报表时间格式改为yyyy-MM-dd HH:mm:ss ffffff 2023-03-28 16:31:53 +08:00
2248356998 qq.com
0432be64fc 采集设备状态判断错误 2023-03-28 16:21:30 +08:00
2248356998 qq.com
481e062961 采集设备状态判断错误 2023-03-28 15:55:01 +08:00
2248356998 qq.com
72b8abdeb6 修复历史保存值 频繁时出现值相同的问题 2023-03-28 15:14:30 +08:00
2248356998 qq.com
7ec3ee41d1 opc 活动时间修正 2023-03-28 14:15:14 +08:00
2248356998 qq.com
b765fa4769 添加写入采集时间的接口,而不是固定DateTime.Now 2023-03-28 14:03:11 +08:00
2248356998 qq.com
06685b162e 更新tcpclient 2023-03-28 13:52:33 +08:00
2248356998 qq.com
7bcad7c424 修复linux 分隔符错误问题 2023-03-28 11:48:16 +08:00
2248356998 qq.com
6e054b3cc6 更改插件文件不存在时的日志信息 2023-03-28 10:21:39 +08:00
2248356998 qq.com
e643f6b0f8 更改项目引用结构 2023-03-28 10:21:23 +08:00
2248356998 qq.com
e44c0f85c2 swagger文档 2023-03-27 19:01:04 +08:00
Iot边缘设备
ea66e968eb !5 添加部署文档
* 添加部署文档
2023-03-27 10:22:30 +00:00
2248356998 qq.com
f7e73d804e 属性/字段缓存获取只包含public 2023-03-27 14:49:09 +08:00
2248356998 qq.com
2ff4df56a1 更新masa-1.0.0-preview.10 2023-03-27 14:27:15 +08:00
2248356998 qq.com
346c45fe0c 优化变量上传属性页 2023-03-27 12:00:10 +08:00
2248356998 qq.com
f98b42a36e 采集设备活动时间应正常刷新 2023-03-27 10:51:54 +08:00
2248356998 qq.com
f77a0a266c 添加设备复制功能 2023-03-27 10:42:02 +08:00
2248356998 qq.com
0be672788f update code collation 2023-03-27 08:49:10 +08:00
2248356998 qq.com
e2e3d11d42 update code collation 2023-03-26 19:48:08 +08:00
2248356998 qq.com
bb127bb567 更新nuget包 2023-03-26 18:52:16 +08:00
2248356998 qq.com
8eb4f89db8 发行1.10版本 2023-03-26 18:10:12 +08:00
2248356998 qq.com
4a87ea3e70 实时数据界面优化 2023-03-26 17:39:05 +08:00
2248356998 qq.com
61115fce99 1、采用异步AutoResetEvent,修复多个设备下采集太慢的问题
2、弃用IntelligentConcurrentQueue(touchsocket集成类)
2023-03-26 17:32:29 +08:00
2248356998 qq.com
4f31120394 1、优化设备状态页,减少signalr刷新所需包大小
2、日志前置等级添加
2023-03-24 18:32:10 +08:00
2248356998 qq.com
b085cd65ce 修复导出表格时,null值报错 2023-03-23 22:09:53 +08:00
2248356998 qq.com
f76ec0721a 导入错误时,忽略大于10行的错误信息显示 2023-03-23 20:01:19 +08:00
2248356998 qq.com
ef3944fbbf 更新readme 2023-03-22 15:29:35 +08:00
2248356998 qq.com
b158fbc0d8 优化log等级、json配置注解 2023-03-22 15:26:42 +08:00
2248356998 qq.com
68f2d66e97 OPCUA写入测试 2023-03-22 14:06:32 +08:00
2248356998 qq.com
0cdd1735bd 设备运行状态页面改为扩展 2023-03-22 13:46:23 +08:00
2248356998 qq.com
70f5ead20b 更新文档 2023-03-22 10:44:44 +08:00
Diego2098
574e5616f8 rbmq插件优化 2023-03-21 23:38:00 +08:00
Diego2098
94fa810590 分包失败提示 2023-03-21 23:28:30 +08:00
Diego2098
37f69da701 设备组列宽调整,补充上传设备组 2023-03-21 23:10:59 +08:00
Diego2098
f2f8448ade 1、更新DataTypeEnum
2、OPCUA写入时需实际数据类型
2023-03-21 22:47:10 +08:00
Diego2098
b357d3fff2 更新Tests 2023-03-21 22:46:12 +08:00
2248356998 qq.com
b417194905 表格无数据时,修改默认宽度 2023-03-21 18:17:18 +08:00
2248356998 qq.com
d8741da20a S7nuget版本 2023-03-21 15:44:50 +08:00
2248356998 qq.com
c469be9a62 masa nuget版本更改 2023-03-21 14:31:36 +08:00
2248356998 qq.com
ff4eb339ef GC策略修改 2023-03-21 14:07:17 +08:00
2248356998 qq.com
78489383c0 更新文档 2023-03-21 14:03:49 +08:00
2248356998 qq.com
f67c1b415f 更新文档 2023-03-21 13:56:14 +08:00
2248356998 qq.com
570c16d921 修改s7属性注释 2023-03-21 13:51:05 +08:00
2248356998 qq.com
c8bc60568a 更新readme 2023-03-21 13:49:10 +08:00
2248356998 qq.com
73de3ba856 更新S7协议插件 2023-03-21 13:45:58 +08:00
2248356998 qq.com
6a69be8537 修改S7特殊方法 2023-03-21 13:41:32 +08:00
2248356998 qq.com
3ee381d505 修改s7 2023-03-21 13:38:43 +08:00
2248356998 qq.com
7ce744e2e4 添加西门子S7协议插件 2023-03-21 13:37:17 +08:00
2248356998 qq.com
b81d21c991 版本恢复 2023-03-21 11:45:18 +08:00
2248356998 qq.com
e9fe0992c6 更新nuget,暂缓链路复用功能 2023-03-20 13:34:59 +08:00
2248356998 qq.com
d5ef9018fa 默认不开启转储 2023-03-20 09:22:11 +08:00
2248356998 qq.com
c2737a7c51 更新opcda文档 2023-03-19 23:18:24 +08:00
2248356998 qq.com
4b6d8733c6 格式清理 2023-03-19 23:12:58 +08:00
2248356998 qq.com
f22a3e0955 修复null值报错 2023-03-19 23:02:08 +08:00
2248356998 qq.com
4b8144a2f7 添加设备组,变量按设备组别搜索 2023-03-19 22:56:02 +08:00
2248356998 qq.com
abac52e23c 更新readme,opcda核心库提示 2023-03-19 20:40:54 +08:00
2248356998 qq.com
9aa089313e 尝试修复中文在mac上乱码 2023-03-19 17:29:01 +08:00
2248356998 qq.com
b18f2c481a 修正中文在mac上乱码 2023-03-19 16:45:14 +08:00
2248356998 qq.com
ec83b9f77b 删除未知文件 2023-03-19 16:35:24 +08:00
2248356998 qq.com
fad5495e02 Merge branch 'master' of https://gitee.com/diego2098/ThingsGateway 2023-03-19 16:34:26 +08:00
2248356998 qq.com
70dec1171e 添加设备组功能 2023-03-19 16:34:15 +08:00
士心
0673a6fce3 !4 修复MacOS环境下,数据库报错的问题
* 修复macos环境下,数据库报错
2023-03-19 08:28:54 +00:00
2248356998 qq.com
8f525b1407 准备更新设备组/变量组功能 2023-03-19 11:49:06 +08:00
2248356998 qq.com
e83a991a4b 更新ReadMe 2023-03-18 17:56:18 +08:00
2248356998 qq.com
c000432a52 1、最后校验失败时应该提示日志
2、暂停时设备状态修正
2023-03-18 17:49:08 +08:00
2248356998 qq.com
c06d2d6927 更新文档 2023-03-18 16:47:29 +08:00
2248356998 qq.com
aa29653a8f ByteBlock实际长度 2023-03-18 16:45:39 +08:00
2248356998 qq.com
3f7e4ad486 修正串口描述类ToString 2023-03-18 16:32:15 +08:00
2248356998 qq.com
0eab546b2f 更新文档 2023-03-18 16:23:23 +08:00
2248356998 qq.com
8830d216d1 更新nuget 2023-03-18 16:16:27 +08:00
2248356998 qq.com
9fa1e3d449 更新readme 2023-03-18 16:15:14 +08:00
2248356998 qq.com
fe5dce7159 添加ModbusRtu种子数据 2023-03-18 16:14:37 +08:00
2248356998 qq.com
952fa31548 添加ModbusRtu插件 2023-03-18 16:14:25 +08:00
2248356998 qq.com
cc058ccc61 添加串口基础类 2023-03-18 16:14:08 +08:00
2248356998 qq.com
bc26ed9701 更新文档 2023-03-17 18:19:14 +08:00
2248356998 qq.com
26135fc1a0 更新文档 2023-03-17 17:52:04 +08:00
2248356998 qq.com
0bd14672ff 种子数据更改 2023-03-17 17:51:58 +08:00
2248356998 qq.com
7c7150cde8 采集检测间隔修改 2023-03-17 17:40:34 +08:00
2248356998 qq.com
b08282b0c1 上传插件添加循环间隔属性 2023-03-17 17:30:06 +08:00
2248356998 qq.com
3bf6e1befc 添加rbmq插件种子数据 2023-03-17 16:30:02 +08:00
2248356998 qq.com
52692371ac rbmq插件文件夹更名 2023-03-17 16:16:23 +08:00
2248356998 qq.com
159ac1d8bb 优化内存queue,修复mqtt插件可能超出字节限制的情况 2023-03-17 16:04:24 +08:00
2248356998 qq.com
7f1ffdbc79 添加rbmq插件 2023-03-17 16:03:44 +08:00
2248356998 qq.com
5fe64931dc 设备禁用时,变量绑定的设备对应选项会显示禁用(灰色) 2023-03-17 11:29:14 +08:00
2248356998 qq.com
ee67855b48 更新readme 2023-03-16 17:33:25 +08:00
2248356998 qq.com
594a97f43f 选择插件时自动更新属性 2023-03-16 17:33:17 +08:00
2248356998 qq.com
7fb435a8b4 更新赞助名单 2023-03-16 11:35:58 +08:00
2248356998 qq.com
01e74d6116 限流服务默认不开启 2023-03-16 09:47:20 +08:00
2248356998 qq.com
b321d75b39 增加简易定时看板 2023-03-15 16:01:54 +08:00
2248356998 qq.com
1e47a45723 更新历史配置说明 2023-03-15 09:11:33 +08:00
Diego2098
88c609e5ef !3 update handbook/docs/05、网关配置/5.5、其他配置.mdx.
Merge pull request !3 from zhubanghao/N/A
2023-03-14 13:39:16 +00:00
zhubanghao
775d1a424e update handbook/docs/05、网关配置/5.5、其他配置.mdx.
Signed-off-by: zhubanghao <58813184@qq.com>
2023-03-14 13:05:33 +00:00
1877 changed files with 131169 additions and 55450 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 +1,20 @@
<Project>
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Version>2.1.0.5</Version>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFrameworks>net6.0;net8.0;</TargetFrameworks>
<Version>4.0.0.9</Version>
<LangVersion>latest</LangVersion>
<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>
<GenerateDocumentationFile>False</GenerateDocumentationFile>
</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.3" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.10" />
</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 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 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;
@inject IRoleService SysRoleService
@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,118 +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; }
[Inject]
IResourceService ResourceService { get; set; }
private string SearchKey { get; set; }
[Inject]
ISysUserService SysUserService { get; set; }
private Task AddCallAsync(RoleAddInput input)
{
return SysRoleService.AddAsync(input);
}
private async Task DeleteCallAsync(IEnumerable<SysRole> sysRoles)
{
await SysRoleService.DeleteAsync(sysRoles.Select(a => a.Id).ToArray());
await MainLayout.StateHasChangedAsync();
}
private async Task EditCallAsync(RoleEditInput input)
{
await SysRoleService.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 SysRoleService.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 SysRoleService.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 SysRoleService.PageAsync(input);
}
private async Task ResuorceInitAsync()
{
ResTreeSelectors = (await ResourceService.GetRoleGrantResourceMenusAsync());
RoleHasResuorces = (await SysRoleService.OwnResourceAsync(ChoiceRoleId))?.GrantInfoList;
}
private async Task<List<UserSelectorOutput>> UserInitAsync()
{
AllUsers = await SysUserService.UserSelectorAsync(SearchKey);
var data = await SysRoleService.OwnUserAsync(ChoiceRoleId);
UsersChoice = AllUsers.Where(a => data.Contains(a.Id)).ToList();
return AllUsers;
}
}

View File

@@ -1,105 +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; }
[Inject]
IRoleService SysRoleService { get; set; }
private Task AddCallAsync(UserAddInput input)
{
return SysUserService.AddAsync(input);
}
private async Task DeleteCallAsync(IEnumerable<SysUser> users)
{
await SysUserService.DeleteAsync(users.Select(a => a.Id).ToArray());
await MainLayout.StateHasChangedAsync();
}
private async Task EditCallAsync(UserEditInput users)
{
await 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 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 SysUserService.PageAsync(input);
}
private async Task ResetPasswordAsync(SysUser sysUser)
{
await SysUserService.ResetPasswordAsync(sysUser.Id);
await PopupService.EnqueueSnackbarAsync(new("成功", AlertTypes.Success));
await MainLayout.StateHasChangedAsync();
}
private async Task RoleInitAsync()
{
AllRoles = await SysRoleService.RoleSelectorAsync();
var data = await SysRoleService.GetRoleIdListByUserIdAsync(ChoiceUserId);
RolesChoice = AllRoles.Where(a => data.Contains(a.Id)).ToList();
}
private async Task UserStatusChangeAsync(SysUser context, bool enable)
{
try
{
if (enable)
await SysUserService.EnableUserAsync(context.Id);
else
await 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,152 +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; }
[Inject]
IAuthService AuthService { 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;
[Inject]
IUserCenterService UserCenterService { get; set; }
[Inject]
IResourceService ResourceService { get; set; }
[Inject]
ISysUserService SysUserService { get; set; }
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 SysUserService.GetIdByAccountAsync(loginModel.Account);
var data = await UserCenterService.GetLoginDefaultRazorAsync(userId);
var sameLevelMenus = await 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 = AuthService.GetCaptchaInfo();
}
private Task<string> RefreshCode()
{
CaptchaInfo = 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.41" />
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.8.8.41" />
<PackageReference Include="Furion.Pure" Version="4.8.8.41" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.102" />
<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,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 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 TGAPPInfo appInfo = new();
/// <summary>
/// 运行信息获取
/// </summary>
public TGAPPInfo APPInfo => appInfo;
/// <summary>
/// IP地址信息
/// </summary>
/// <returns></returns>
public async Task<string> GetIpFromOnlineAsync()
{
try
{
var url = "http://myip.ipip.net";
var stream = await new HttpClient().GetStreamAsync(url);
var streamReader = new StreamReader(stream, Encoding.UTF8);
var html = streamReader.ReadToEnd();
return html.Replace("当前 IP", "").Replace("来自于:", "");
}
catch (Exception)
{
return "";
}
}
}
/// <inheritdoc/>
public class TGAPPInfo
{
/// <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,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,492 +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": "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,523 +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);
return memoryStream;
}
/// <inheritdoc/>
public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file)
{
_fileService.ImportVerification(file);
using var fs = new MemoryStream();
using var stream = file.OpenReadStream(512000000);
await stream.CopyToAsync(fs);
var sheetNames = MiniExcel.GetSheetNames(fs);
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 = fs.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 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,440 +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="IUploadDeviceService"/>
[Injection(Proxy = typeof(OperDispatchProxy))]
public class UploadDeviceService : DbRepository<UploadDevice>, IUploadDeviceService
{
private readonly IDriverPluginService _driverPluginService;
private readonly IFileService _fileService;
/// <inheritdoc cref="IUploadDeviceService"/>
public UploadDeviceService(
IDriverPluginService driverPluginService,
IFileService fileService
)
{
_fileService = fileService;
_driverPluginService = driverPluginService;
}
/// <inheritdoc/>
[OperDesc("添加上传设备")]
public async Task AddAsync(UploadDevice input)
{
var account_Id = GetIdByName(input.Name);
if (account_Id > 0)
throw Oops.Bah($"存在重复的名称:{input.Name}");
await InsertAsync(input);//添加数据
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_UploadDevice);//cache删除
}
/// <inheritdoc/>
[OperDesc("复制上传设备")]
public async Task CopyDevAsync(IEnumerable<UploadDevice> input)
{
var newId = YitIdHelper.NextId();
var newDevs = input.Adapt<List<UploadDevice>>();
newDevs.ForEach(a =>
{
a.Id = newId;
a.Name = "Copy-" + a.Name + "-" + newId.ToString();
});
var result = await InsertRangeAsync(newDevs);//添加数据
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_UploadDevice);//cache删除
}
/// <inheritdoc/>
[OperDesc("删除上传设备")]
public async Task DeleteAsync(params long[] input)
{
//获取所有ID
if (input.Length > 0)
{
var result = await DeleteByIdsAsync(input.Cast<object>().ToArray());
if (result)
{
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_UploadDevice);//cache删除
}
}
}
/// <inheritdoc/>
[OperDesc("编辑上传设备")]
public async Task EditAsync(UploadDeviceEditInput 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<UploadDevice>()).ExecuteCommandAsync() > 0)//修改数据
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_UploadDevice);//cache删除
}
/// <inheritdoc cref="IUploadDeviceService"/>
public List<UploadDevice> GetCacheList(bool isMapster = true)
{
//先从Cache拿
var uploadDevice = CacheStatic.Cache.Get<List<UploadDevice>>(ThingsGatewayCacheConst.Cache_UploadDevice, isMapster);
if (uploadDevice == null)
{
uploadDevice = Context.Queryable<UploadDevice>().ToList();
if (uploadDevice != null)
{
//插入Cache
CacheStatic.Cache.Set(ThingsGatewayCacheConst.Cache_UploadDevice, uploadDevice, isMapster);
}
}
return uploadDevice;
}
/// <inheritdoc/>
public UploadDevice GetDeviceById(long Id)
{
var data = GetCacheList();
return data.FirstOrDefault(it => it.Id == Id);
}
/// <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<UploadDeviceRunTime> GetUploadDeviceRuntime(long devId = 0)
{
ConcurrentDictionary<long, DriverPlugin> driverPlugins = new(_driverPluginService.GetCacheList(false).ToDictionary(a => a.Id));
if (devId == 0)
{
var devices = GetCacheList(false).Where(a => a.Enable).ToList();
var runtime = devices.Adapt<List<UploadDeviceRunTime>>();
foreach (var device in runtime)
{
driverPlugins.TryGetValue(device.PluginId, out var driverPlugin);
device.PluginName = driverPlugin?.AssembleName;
}
return runtime;
}
else
{
var devices = GetCacheList(false).Where(a => a.Enable).ToList();
devices = devices.Where(it => it.Id == devId).ToList();
var runtime = devices.Adapt<List<UploadDeviceRunTime>>();
foreach (var device in runtime)
{
driverPlugins.TryGetValue(device.PluginId, out var driverPlugin);
device.PluginName = driverPlugin?.AssembleName;
}
return runtime;
}
}
/// <inheritdoc/>
public async Task<SqlSugarPagedList<UploadDevice>> PageAsync(UploadDevicePageInput input)
{
var query = GetPage(input);
var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页
return pageInfo;
}
/// <inheritdoc/>
private ISugarQueryable<UploadDevice> GetPage(UploadDevicePageInput input)
{
long? pluginid = 0;
if (!string.IsNullOrEmpty(input.PluginName))
{
pluginid = _driverPluginService.GetCacheList(false).FirstOrDefault(it => it.AssembleName.Contains(input.PluginName))?.Id;
}
var query = Context.Queryable<UploadDevice>()
.WhereIF(!string.IsNullOrEmpty(input.Name), u => u.Name.Contains(input.Name))
.WhereIF(!string.IsNullOrEmpty(input.PluginName), u => u.PluginId == (pluginid ?? 0))
.WhereIF(!string.IsNullOrEmpty(input.DeviceGroup), u => u.DeviceGroup == input.DeviceGroup);
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;
}
#region
/// <inheritdoc/>
public async Task<MemoryStream> ExportFileAsync(UploadDeviceInput input)
{
var query = GetPage(input.Adapt<UploadDevicePageInput>());
var data = await query.ToListAsync();
return await ExportFileAsync(data);
}
/// <inheritdoc/>
[OperDesc("导出上传设备表", IsRecordPar = false)]
public async Task<MemoryStream> ExportFileAsync(List<UploadDevice> 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();
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());
}
//设备实体没有包含插件名称,手动插入
devExport.Add(ExportHelpers.PluginName, _driverPluginService.GetNameById(devData.PluginId));
//添加完整设备信息
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 = _driverPluginService.GetNameById(devData.PluginId).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.UploadDeviceSheetName, devExports);
//添加插件属性页
foreach (var item in devicePropertys)
{
sheets.Add(item.Key, item.Value);
}
var memoryStream = new MemoryStream();
await memoryStream.SaveAsAsync(sheets);
return memoryStream;
}
/// <inheritdoc/>
[OperDesc("导入上传设备表", IsRecordPar = false)]
public async Task ImportAsync(Dictionary<string, ImportPreviewOutputBase> input)
{
var uploadDevices = new List<UploadDevice>();
foreach (var item in input)
{
if (item.Key == ExportHelpers.UploadDeviceSheetName)
{
var uploadDeviceImports = ((ImportPreviewOutput<UploadDevice>)item.Value).Data;
uploadDevices = uploadDeviceImports.Values.Adapt<List<UploadDevice>>();
break;
}
}
await Context.Storageable(uploadDevices).ExecuteCommandAsync();
CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_UploadDevice);//cache删除
}
/// <inheritdoc/>
public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file)
{
_fileService.ImportVerification(file);
using var fs = new MemoryStream();
using var stream = file.OpenReadStream(512000000);
await stream.CopyToAsync(fs);
var sheetNames = MiniExcel.GetSheetNames(fs);
var deviceDicts = GetCacheList(false).ToDictionary(a => a.Name);
var pluginDicts = _driverPluginService.GetCacheList(false).ToDictionary(a => a.AssembleName);
//导入检验结果
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
//设备页
ImportPreviewOutput<UploadDevice> deviceImportPreview = new();
foreach (var sheetName in sheetNames)
{
//单页数据
var rows = (fs.Query(useHeaderRow: true, sheetName: sheetName)).Cast<IDictionary<string, object>>();
#region sheet
if (sheetName == ExportHelpers.UploadDeviceSheetName)
{
int row = 1;
ImportPreviewOutput<UploadDevice> importPreviewOutput = new();
ImportPreviews.Add(sheetName, importPreviewOutput);
deviceImportPreview = importPreviewOutput;
List<UploadDevice> devices = new();
rows.ForEach(item =>
{
try
{
var device = ((ExpandoObject)item).ConvertToEntity<UploadDevice>(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;
}
#endregion
//插件ID、设备ID都需要手动补录
device.PluginId = plugin.Id;
device.Id = deviceDicts.TryGetValue(device.Name, out var uploadDevice) ? uploadDevice.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 = 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 ImportPreviews;
}
#endregion
}

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,48 +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" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
<ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,683 +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.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 = ServiceHelper.Services.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>
public 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");
CancellationTokenSource stoppingToken = StoppingTokens.Last();
RealAlarmTask = await Task.Factory.StartNew(async () =>
{
_logger?.LogInformation($"实时报警线程开始");
while (!stoppingToken.Token.IsCancellationRequested)
{
try
{
await Task.Delay(500, stoppingToken.Token);
var list = DeviceVariables.ToListWithDequeue();
foreach (var item in list)
{
if (stoppingToken.Token.IsCancellationRequested)
break;
if (!item.AlarmEnable) continue;
AlarmAnalysis(item);
}
if (stoppingToken.Token.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.Token);
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.Token);
isSuccess = true;
StatuString = OperResult.CreateSuccessResult();
}
catch (Exception)
{
if (stoppingToken.Token.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.Token.IsCancellationRequested)
{
try
{
await Task.Delay(500, stoppingToken.Token);
if (stoppingToken.Token.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.Token);
await CacheDb.DeleteCacheData(cacheData.Select(a => a.Id).ToArray());
}
catch (Exception ex)
{
if (isSuccess)
_logger.LogWarning(ex, "写入历史报警失败");
}
}
if (stoppingToken.Token.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.Token);
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,776 +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.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 = ServiceHelper.Services.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
{
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;
_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>
public 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()
{
CancellationTokenSource stoppingToken = StoppingTokens.Last();
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.Token, channelResult.Content);
}
else
{
await device.BeforeActionAsync(stoppingToken.Token);
}
}
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.Token);
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.Token);
}
}
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,567 +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.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 = ServiceHelper.Services.GetService<ICollectDeviceService>();
}
/// <summary>
/// 读取未停止的采集设备List
/// </summary>
public List<CollectDeviceCore> CollectDeviceCores => CollectDeviceThreads
.Where(a => !a.StoppingTokens.Any(b => b.IsCancellationRequested))
.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 StartAllDeviceThreadsAsync();
//开始其他后台服务
await StartOtherHostService();
}
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 = ServiceHelper.Services.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 = ServiceHelper.Services.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 = ServiceHelper.Services.GetService<IDriverPluginService>();
var driverPlugin = driverPluginService.GetDriverPluginById(driverId);
var driver = _pluginService.GetDriver(driverPlugin);
var Propertys = _pluginService.GetDriverProperties(driver);
if (devId != 0)
{
var devcore = CollectDeviceCores.FirstOrDefault(it => it.Device.Id == devId);
devcore?.Device?.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,484 +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.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 = ServiceHelper.Services.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>
public 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");
CancellationTokenSource stoppingToken = StoppingTokens.Last();
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.Token);
LastIsSuccess = true;
StatuString = OperResult.CreateSuccessResult();
}
catch (Exception)
{
if (stoppingToken.Token.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.Token.IsCancellationRequested)
{
try
{
await Task.Delay(500, stoppingToken.Token);
if (stoppingToken.Token.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.Token);
await CacheDb.DeleteCacheData(cacheData.Select(a => a.Id).ToArray());
}
catch (Exception ex)
{
if (LastIsSuccess)
_logger.LogWarning(ex, "写入历史数据失败");
}
if (stoppingToken.Token.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.Token);
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.Token.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.Token);
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,254 +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.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>
public ConcurrentList<CancellationTokenSource> StoppingTokens = new();
private Task MemoryWorkerTask;
/// <summary>
/// 全部重启锁
/// </summary>
private readonly EasyLock restartLock = new();
/// <summary>
/// 初始化
/// </summary>
public async Task InitAsync()
{
CancellationTokenSource stoppingToken = StoppingTokens.Last();
MemoryWorkerTask = await Task.Factory.StartNew(async () =>
{
_logger?.LogInformation($"中间变量计算线程开始");
try
{
var variableService = ServiceHelper.Services.GetService<IVariableService>();
var data = await variableService.GetMemoryVariableRuntimeAsync();
_globalDeviceData.MemoryVariables = new(data);
StatuString = OperResult.CreateSuccessResult();
while (!stoppingToken.Token.IsCancellationRequested)
{
try
{
await Task.Delay(500, stoppingToken.Token);
if (stoppingToken.Token.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,330 +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.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 = ServiceHelper.Services.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>
public 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()
{
CancellationTokenSource stoppingToken = StoppingTokens.Last();
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.Token);
}
while (!stoppingToken.IsCancellationRequested)
{
foreach (var device in UploadDeviceCores)
{
try
{
if (stoppingToken.IsCancellationRequested)
break;
//初始化成功才能执行
if (device.IsInitSuccess)
{
var result = await device.RunActionAsync(stoppingToken.Token);
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,435 +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.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 = ServiceHelper.Services.GetService<IUploadDeviceService>();
}
/// <summary>
/// 上传设备List
/// </summary>
public List<UploadDeviceCore> UploadDeviceCores => UploadDeviceThreads
.Where(a => !a.StoppingTokens.Any(b => b.IsCancellationRequested))
.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 = ServiceHelper.Services.GetService<IDriverPluginService>();
var driverPlugin = driverPluginService.GetDriverPluginById(driverId);
var driver = _pluginService.GetDriver(driverPlugin);
var Propertys = _pluginService.GetDriverProperties(driver);
if (devId != 0)
{
var devcore = UploadDeviceCores.FirstOrDefault(it => it.Device.Id == devId);
devcore?.Device?.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 = ServiceHelper.Services.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,259 +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 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 ICollectDeviceService CollectDeviceService { get; set; }
[Inject]
private IVariableService VariableService { 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 CollectDeviceService.AddAsync(data);
}
finally
{
isDownExport = false;
}
}
/// <summary>
/// 导入变量
/// </summary>
/// <returns></returns>
public async Task DeviceVariableImportAsync(List<DeviceVariable> data)
{
try
{
isDownExport = true;
StateHasChanged();
await 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();
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 CollectDeviceService.ExportFileAsync(new List<CollectDevice>() { data });
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()}.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 VariableService.ExportFileAsync(data, devName);
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()}.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,303 +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 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;
@inject ICollectDeviceService CollectDeviceService
@attribute [Authorize]
@inherits BaseComponentBase
@inject UserResoures UserResoures
@inject IDriverPluginService DriverPluginService
@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>
@(
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 CollectDeviceService.AddAsync(input);
CollectDevices = 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 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 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 CollectDeviceService.DeleteAsync(input.Select(a => a.Id).ToArray());
CollectDevices = 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 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 CollectDeviceService.EditAsync(input);
CollectDevices = 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 CollectDeviceService.PageAsync(input);
return data;
}
async Task SaveDeviceImportAsync(Dictionary<string, ImportPreviewOutputBase> data)
{
await CollectDeviceService.ImportAsync(data);
await DatatableQueryAsync();
ImportExcel.IsShowImport = false;
await MainLayout.StateHasChangedAsync();
}
}

View File

@@ -1,162 +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;
@inject IConfigService ConfigService
@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 ConfigService.EditBatchAsync(_alarmConfig);
await AlarmHostService.RestartAsync();
await PopupService.EnqueueSnackbarAsync("成功", AlertTypes.Success);
}
}
private async Task OnHisSaveAsync()
{
var confirm = await PopupService.OpenConfirmDialogAsync("确认", "保存配置后将重启历史服务,是否确定?");
if (confirm)
{
await ConfigService.EditBatchAsync(_hisConfig);
await HistoryValueHostService.RestartAsync();
await PopupService.EnqueueSnackbarAsync("成功", AlertTypes.Success);
}
}
}

View File

@@ -1,630 +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;
@inject ICollectDeviceService CollectDeviceService
@inject IUploadDeviceService UploadDeviceService
@attribute [Authorize]
@inherits BaseComponentBase
@inject UserResoures UserResoures
@inject NavigationManager NavigationManager
@inject IDriverPluginService DriverPluginService
@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 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,276 +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; }
[Inject]
IVariableService VariableService { 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();
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,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 BlazorComponent;
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();
[Inject]
IDriverPluginService DriverPluginService { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
DriverPlugins = 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,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,131 +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; }
[Inject]
InitTimezone InitTimezone { get; set; }
[Inject]
IVariableService VariableService { 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 VariableService.AddAsync(input);
}
private async Task ClearAsync()
{
var confirm = await PopupService.OpenConfirmDialogAsync("确认", "清空?");
if (confirm)
{
await VariableService.ClearMemoryVariableAsync();
}
await DatatableQueryAsync();
}
private async Task DatatableQueryAsync()
{
await _datatable.QueryClickAsync();
}
private async Task DeleteCallAsync(IEnumerable<DeviceVariable> input)
{
await VariableService.DeleteAsync(input.Select(a => a.Id).ToArray());
}
Task<Dictionary<string, ImportPreviewOutputBase>> DeviceImportAsync(IBrowserFile file)
{
return 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 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 VariableService.PageAsync(input);
return data;
}
async Task SaveDeviceImportAsync(Dictionary<string, ImportPreviewOutputBase> data)
{
await VariableService.ImportAsync(data);
await DatatableQueryAsync();
ImportExcel.IsShowImport = false;
}
}

View File

@@ -1,263 +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 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>
@(
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,155 +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; }
[Inject]
IDriverPluginService DriverPluginService { get; set; }
[Inject]
InitTimezone InitTimezone { get; set; }
[CascadingParameter]
MainLayout MainLayout { get; set; }
[Inject]
IUploadDeviceService UploadDeviceService { 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 UploadDeviceService.AddAsync(input);
_deviceGroups = 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 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 UploadDeviceService.DeleteAsync(input.Select(a => a.Id).ToArray());
_deviceGroups = UploadDeviceService.GetCacheList()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList();
await MainLayout.StateHasChangedAsync();
}
Task<Dictionary<string, ImportPreviewOutputBase>> DeviceImportAsync(IBrowserFile file)
{
return 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 UploadDeviceService.EditAsync(input);
_deviceGroups = 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 UploadDeviceService.PageAsync(input);
return data;
}
async Task SaveDeviceImportAsync(Dictionary<string, ImportPreviewOutputBase> data)
{
await 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>

View File

@@ -1,469 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>ThingsGateway.Blazor</name>
</assembly>
<members>
<member name="T:ThingsGateway.Blazor.ImportExcel">
<summary>
导入excel
</summary>
</member>
<member name="P:ThingsGateway.Blazor.ImportExcel.Import">
<summary>
导入
</summary>
</member>
<member name="P:ThingsGateway.Blazor.ImportExcel.IsShowImport">
<summary>
是否显示
</summary>
</member>
<member name="P:ThingsGateway.Blazor.ImportExcel.Preview">
<summary>
预览
</summary>
</member>
<member name="P:ThingsGateway.Blazor.ImportExcel.Step">
<summary>
当前步数
</summary>
</member>
<member name="T:ThingsGateway.Blazor.HisPageInput">
<summary>
历史查询条件
</summary>
</member>
<member name="P:ThingsGateway.Blazor.HisPageInput.StartTime">
<summary>
开始时间
</summary>
</member>
<member name="P:ThingsGateway.Blazor.HisPageInput.EndTime">
<summary>
结束时间
</summary>
</member>
<member name="T:ThingsGateway.Blazor.ChannelEnum">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="F:ThingsGateway.Blazor.ChannelEnum.None">
<inheritdoc/>
</member>
<member name="F:ThingsGateway.Blazor.ChannelEnum.TcpClientEx">
<inheritdoc/>
</member>
<member name="F:ThingsGateway.Blazor.ChannelEnum.SerialPort">
<inheritdoc/>
</member>
<member name="F:ThingsGateway.Blazor.ChannelEnum.UdpSession">
<inheritdoc/>
</member>
<member name="F:ThingsGateway.Blazor.ChannelEnum.TcpServer">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Blazor.DefalutDebugDriverPage">
<inheritdoc/>
</member>
<member name="F:ThingsGateway.Blazor.DefalutDebugDriverPage.SerialSessionPage">
<summary>
SerialSessionPage
</summary>
</member>
<member name="F:ThingsGateway.Blazor.DefalutDebugDriverPage.TcpClientPage">
<summary>
TcpClientPage
</summary>
</member>
<member name="F:ThingsGateway.Blazor.DefalutDebugDriverPage.TcpServerPage">
<summary>
TcpServerPage
</summary>
</member>
<member name="F:ThingsGateway.Blazor.DefalutDebugDriverPage.UdpSessionPage">
<summary>
UdpSessionPage
</summary>
</member>
<member name="P:ThingsGateway.Blazor.DefalutDebugDriverPage.Channel">
<summary>
选择1-TCPCLIENT2-串口3-UDP4-TCPServer
</summary>
</member>
<member name="P:ThingsGateway.Blazor.DefalutDebugDriverPage.Plc">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="P:ThingsGateway.Blazor.DefalutDebugDriverPage.ChildContent">
<summary>
模板
</summary>
</member>
<member name="P:ThingsGateway.Blazor.DefalutDebugDriverPage.OtherContent">
<summary>
自定义模板
</summary>
</member>
<member name="M:ThingsGateway.Blazor.DefalutDebugDriverPage.Dispose">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Blazor.DefalutDebugDriverPage.OnAfterRender(System.Boolean)">
<summary>
<inheritdoc/>
</summary>
<param name="firstRender"></param>
</member>
<member name="T:ThingsGateway.Blazor.SerialSessionPage">
<inheritdoc/>
</member>
<member name="F:ThingsGateway.Blazor.SerialSessionPage.LogAction">
<summary>
日志输出
</summary>
</member>
<member name="M:ThingsGateway.Blazor.SerialSessionPage.Dispose">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Blazor.SerialSessionPage.GetSerialSession">
<summary>
获取对象
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Blazor.SerialSessionPage.OnInitialized">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Blazor.TcpClientPage">
<inheritdoc/>
</member>
<member name="F:ThingsGateway.Blazor.TcpClientPage.LogAction">
<summary>
日志输出
</summary>
</member>
<member name="F:ThingsGateway.Blazor.TcpClientPage.IP">
<summary>
IP
</summary>
</member>
<member name="P:ThingsGateway.Blazor.TcpClientPage.Port">
<summary>
端口
</summary>
</member>
<member name="M:ThingsGateway.Blazor.TcpClientPage.Dispose">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Blazor.TcpClientPage.GetTcpClient">
<summary>
获取对象
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Blazor.TcpClientPage.OnInitialized">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Blazor.TcpServerPage">
<inheritdoc/>
</member>
<member name="F:ThingsGateway.Blazor.TcpServerPage.LogAction">
<summary>
日志输出
</summary>
</member>
<member name="M:ThingsGateway.Blazor.TcpServerPage.Dispose">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Blazor.TcpServerPage.GetTcpServer">
<summary>
获取对象
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Blazor.TcpServerPage.OnInitialized">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="T:ThingsGateway.Blazor.UdpSessionPage">
<inheritdoc/>
</member>
<member name="F:ThingsGateway.Blazor.UdpSessionPage.LogAction">
<summary>
日志输出
</summary>
</member>
<member name="M:ThingsGateway.Blazor.UdpSessionPage.Dispose">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Blazor.UdpSessionPage.GetUdpSession">
<summary>
获取对象
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Blazor.UdpSessionPage.OnInitialized">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="T:ThingsGateway.Blazor.BackendLogPage">
<summary>
后台日志页面
</summary>
</member>
<member name="T:ThingsGateway.Blazor.CollectDevicePage">
<summary>
采集设备页面
</summary>
</member>
<member name="M:ThingsGateway.Blazor.CollectDevicePage.OnParametersSetAsync">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Blazor.ConfigPage">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Blazor.ConfigPage.#ctor">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Blazor.ConfigPage.OnParametersSetAsync">
<summary>
<inheritdoc/>
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Blazor.DeviceStatusPage">
<summary>
设备状态页面
</summary>
</member>
<member name="M:ThingsGateway.Blazor.DeviceStatusPage.Dispose">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="M:ThingsGateway.Blazor.DeviceStatusPage.OnInitialized">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Blazor.DeviceStatusPage.OnParametersSet">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="T:ThingsGateway.Blazor.DeviceVariablePage">
<summary>
采集变量页面
</summary>
</member>
<member name="M:ThingsGateway.Blazor.DeviceVariablePage.OnParametersSetAsync">
<summary>
<inheritdoc/>
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Blazor.DeviceVariableRunTimePage">
<summary>
实时数据页
</summary>
</member>
<member name="P:ThingsGateway.Blazor.DeviceVariableRunTimePage.DeviceName">
<summary>
设备名称
</summary>
</member>
<member name="P:ThingsGateway.Blazor.DeviceVariableRunTimePage.UploadDeviceName">
<summary>
上传设备名称
</summary>
</member>
<member name="M:ThingsGateway.Blazor.DeviceVariableRunTimePage.Dispose">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="M:ThingsGateway.Blazor.DeviceVariableRunTimePage.OnInitialized">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Blazor.DeviceVariableRunTimePage.OnParametersSetAsync">
<summary>
<inheritdoc/>
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Blazor.DriverDebugPage">
<summary>
调试页面
</summary>
</member>
<member name="M:ThingsGateway.Blazor.DriverDebugPage.OnInitialized">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="M:ThingsGateway.Blazor.DriverDebugPage.ImportVaiableAsync(System.Int64)">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Blazor.DriverPluginPage">
<summary>
插件管理页面
</summary>
</member>
<member name="T:ThingsGateway.Blazor.HardwareInfoPage">
<summary>
硬件信息页面
</summary>
</member>
<member name="M:ThingsGateway.Blazor.HardwareInfoPage.OnInitialized">
<summary>
<inheritdoc/>
</summary>
</member>
<member name="T:ThingsGateway.Blazor.HistoryAlarmPage">
<summary>
历史报警页面
</summary>
</member>
<member name="M:ThingsGateway.Blazor.HistoryAlarmPage.OnInitializedAsync">
<summary>
<inheritdoc/>
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Blazor.HistoryValuePage">
<summary>
时序库页面
</summary>
</member>
<member name="M:ThingsGateway.Blazor.HistoryValuePage.OnInitializedAsync">
<summary>
<inheritdoc/>
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Blazor.MemoryVariablePage">
<summary>
内存变量页面
</summary>
</member>
<member name="M:ThingsGateway.Blazor.MemoryVariablePage.OnParametersSetAsync">
<summary>
<inheritdoc/>
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Blazor.RealAlarmPage">
<summary>
实时报警
</summary>
</member>
<member name="M:ThingsGateway.Blazor.RealAlarmPage.Dispose">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Blazor.RealAlarmPage.OnInitialized">
<inheritdoc/>
</member>
<member name="T:ThingsGateway.Blazor.RpcLogPage">
<summary>
RPC日志页面
</summary>
</member>
<member name="T:ThingsGateway.Blazor.UploadDevicePage">
<summary>
上传设备页
</summary>
</member>
<member name="M:ThingsGateway.Blazor.UploadDevicePage.OnParametersSetAsync">
<summary>
<inheritdoc/>
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Application.DriverDebugUIBase">
<summary>
调试UI
</summary>
</member>
<member name="F:ThingsGateway.Application.DriverDebugUIBase.isDownExport">
<summary>
导出提示
</summary>
</member>
<member name="F:ThingsGateway.Application.DriverDebugUIBase.Messages">
<summary>
日志缓存
</summary>
</member>
<member name="P:ThingsGateway.Application.DriverDebugUIBase.Plc">
<summary>
默认读写设备
</summary>
</member>
<member name="P:ThingsGateway.Application.DriverDebugUIBase.Address">
<summary>
变量地址
</summary>
</member>
<member name="P:ThingsGateway.Application.DriverDebugUIBase.DataTypeEnum">
<summary>
数据类型
</summary>
</member>
<member name="P:ThingsGateway.Application.DriverDebugUIBase.JS">
<inheritdoc/>
</member>
<member name="P:ThingsGateway.Application.DriverDebugUIBase.WriteValue">
<summary>
写入值
</summary>
</member>
<member name="M:ThingsGateway.Application.DriverDebugUIBase.Dispose">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Application.DriverDebugUIBase.ReadAsync">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Application.DriverDebugUIBase.WriteAsync">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Application.DriverDebugUIBase.DeviceImportAsync(ThingsGateway.Application.CollectDevice)">
<summary>
导入设备
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Application.DriverDebugUIBase.DeviceVariableImportAsync(System.Collections.Generic.List{ThingsGateway.Application.DeviceVariable})">
<summary>
导入变量
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Application.DriverDebugUIBase.DownDeviceMessageExportAsync(System.Collections.Generic.IEnumerable{System.String})">
<summary>
导出
</summary>
<param name="values"></param>
<returns></returns>
</member>
<member name="M:ThingsGateway.Application.DriverDebugUIBase.DownDeviceExportAsync(ThingsGateway.Application.CollectDevice)">
<summary>
导出到excel
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Application.DriverDebugUIBase.DownDeviceVariableExportAsync(System.Collections.Generic.List{ThingsGateway.Application.DeviceVariable},System.String)">
<summary>
导出到excel
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Application.DriverDebugUIBase.LogOut(TouchSocket.Core.LogLevel,System.Object,System.String,System.Exception)">
<inheritdoc/>
</member>
<member name="M:ThingsGateway.Application.DriverDebugUIBase.OnInitialized">
<inheritdoc/>
</member>
</members>
</doc>

View File

@@ -1,48 +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.Foundation;
/// <summary>
/// 设备地址数据的信息,对每个协议都建立其变量地址的表示类
/// </summary>
public abstract class DeviceAddressBase
{
/// <summary>
/// 起始地址
/// </summary>
public int AddressStart { get; set; }
/// <summary>
/// 读取的数据长度,有可能不参与运算
/// </summary>
public int Length { get; set; }
/// <summary>
/// 字符串地址转换为实体类
/// </summary>
/// <param name="address">变量地址</param>
/// <param name="length">读取长度</param>
public virtual void Parse(string address, int length)
{
AddressStart = int.Parse(address);
Length = length;
}
/// <summary>
/// 实体类转换为字符串
/// </summary>
/// <returns></returns>
public override string ToString()
{
return AddressStart.ToString();
}
}

View File

@@ -1,107 +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.Foundation;
/// <summary>
/// 读写接口
/// </summary>
public interface IReadWrite
{
/// <summary>
/// 日志
/// </summary>
ILog Logger { get; }
/// <summary>
/// 获取变量地址对应的bit偏移
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
int GetBitOffset(string address);
/// <summary>
/// 异步批量读取字节数组信息,需要指定地址和长度<br></br>
/// 如果需要固定的类型返回,可以使用<see cref="ReadWriteDevicesExHelpers.ReadAsync(IReadWriteDevice, string, CancellationToken)"/><br></br>
/// </summary>
Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken token = default);
/// <summary>
/// 发送字节组,返回结果
/// </summary>
OperResult<byte[]> SendThenResponse(byte[] data, WaitingOptions waitingOptions = null, CancellationToken token = default);
/// <summary>
/// 异步发送字节组,返回结果
/// </summary>
Task<OperResult<byte[]>> SendThenResponseAsync(byte[] data, WaitingOptions waitingOptions = null, CancellationToken token = default);
/// <summary>
/// 异步写入原始的byte数组数据到指定的地址返回结果
/// </summary>
Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken token = default);
/// <summary>
/// 异步写入bool数组数据返回结果
/// </summary>
Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken token = default);
/// <summary>
/// 异步写入bool数据返回结果
/// </summary>
Task<OperResult> WriteAsync(string address, bool value, CancellationToken token = default);
/// <summary>
/// 异步写入short数据返回结果
/// </summary>
Task<OperResult> WriteAsync(string address, short value, CancellationToken token = default);
/// <summary>
/// 异步写入ushort数据返回结果
/// </summary>
Task<OperResult> WriteAsync(string address, ushort value, CancellationToken token = default);
/// <summary>
/// 异步写入int数据返回结果
/// </summary>
Task<OperResult> WriteAsync(string address, int value, CancellationToken token = default);
/// <summary>
/// 异步写入uint数据返回结果
/// </summary>
Task<OperResult> WriteAsync(string address, uint value, CancellationToken token = default);
/// <summary>
/// 异步写入long数据返回结果
/// </summary>
Task<OperResult> WriteAsync(string address, long value, CancellationToken token = default);
/// <summary>
/// 异步写入ulong数据返回结果
/// </summary>
Task<OperResult> WriteAsync(string address, ulong value, CancellationToken token = default);
/// <summary>
/// 异步写入float数据返回结果
/// </summary>
Task<OperResult> WriteAsync(string address, float value, CancellationToken token = default);
/// <summary>
/// 异步写入double数据返回结果
/// </summary>
Task<OperResult> WriteAsync(string address, double value, CancellationToken token = default);
/// <summary>
/// 异步写入字符串信息
/// </summary>
Task<OperResult> WriteAsync(string address, string value, CancellationToken token = default);
}

View File

@@ -1,169 +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.Text;
using ThingsGateway.Foundation.Extension.String;
namespace ThingsGateway.Foundation;
/// <summary>
/// 读写设备基类
/// </summary>
public abstract class ReadWriteDevicesBase : DisposableObject, IReadWriteDevice
{
/// <inheritdoc/>
[Description("数据转换")]
public DataFormat DataFormat
{
get => ThingsGatewayBitConverter.DataFormat;
set => ThingsGatewayBitConverter.DataFormat = value;
}
/// <inheritdoc/>
public ILog Logger { get; protected set; }
/// <inheritdoc/>
public IThingsGatewayBitConverter ThingsGatewayBitConverter { get; protected set; } = new ThingsGatewayBitConverter(EndianType.Big);
/// <inheritdoc/>
[Description("读写超时")]
public ushort TimeOut { get; set; } = 3000;
/// <inheritdoc/>
public ushort RegisterByteLength { get; protected set; } = 1;
/// <inheritdoc/>
public virtual int GetBitOffset(string address)
{
int bitIndex = 0;
if (address?.IndexOf('.') > 0)
{
try
{
bitIndex = Convert.ToInt32(address?.SplitDot()?.Last());
}
catch
{
return 0;
}
}
return bitIndex;
}
/// <inheritdoc/>
public abstract Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken token = default);
/// <inheritdoc/>
public abstract Task<OperResult<byte[]>> SendThenResponseAsync(byte[] data, WaitingOptions waitingOptions = null, CancellationToken token = default);
/// <inheritdoc/>
public abstract OperResult<byte[]> SendThenResponse(byte[] data, WaitingOptions waitingOptions = null, CancellationToken token = default);
/// <inheritdoc/>
public abstract Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken token = default);
/// <inheritdoc/>
public abstract Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken token = default);
/// <inheritdoc/>
public virtual Task<OperResult> WriteAsync(string address, bool value, CancellationToken token = default)
{
return WriteAsync(address, new bool[1] { value }, token);
}
/// <inheritdoc/>
public virtual Task<OperResult> WriteAsync(string address, short value, CancellationToken token = default)
{
IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, ThingsGatewayBitConverter);
return WriteAsync(address, transformParameter.GetBytes(value), token);
}
/// <inheritdoc/>
public virtual Task<OperResult> WriteAsync(string address, byte value, CancellationToken token = default)
{
return WriteAsync(address, new byte[1] { value }, token);
}
/// <inheritdoc/>
public virtual Task<OperResult> WriteAsync(string address, ushort value, CancellationToken token = default)
{
IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, ThingsGatewayBitConverter);
return WriteAsync(address, transformParameter.GetBytes(value), token);
}
/// <inheritdoc/>
public virtual Task<OperResult> WriteAsync(string address, int value, CancellationToken token = default)
{
IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, ThingsGatewayBitConverter);
return WriteAsync(address, transformParameter.GetBytes(value), token);
}
/// <inheritdoc/>
public virtual Task<OperResult> WriteAsync(string address, uint value, CancellationToken token = default)
{
IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, ThingsGatewayBitConverter);
return WriteAsync(address, transformParameter.GetBytes(value), token);
}
/// <inheritdoc/>
public virtual Task<OperResult> WriteAsync(string address, long value, CancellationToken token = default)
{
IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, ThingsGatewayBitConverter);
return WriteAsync(address, transformParameter.GetBytes(value), token);
}
/// <inheritdoc/>
public virtual Task<OperResult> WriteAsync(string address, ulong value, CancellationToken token = default)
{
IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, ThingsGatewayBitConverter);
return WriteAsync(address, transformParameter.GetBytes(value), token);
}
/// <inheritdoc/>
public virtual Task<OperResult> WriteAsync(string address, float value, CancellationToken token = default)
{
IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, ThingsGatewayBitConverter);
return WriteAsync(address, transformParameter.GetBytes(value), token);
}
/// <inheritdoc/>
public virtual Task<OperResult> WriteAsync(string address, double value, CancellationToken token = default)
{
IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, ThingsGatewayBitConverter);
return WriteAsync(address, transformParameter.GetBytes(value), token);
}
/// <inheritdoc/>
public virtual Task<OperResult> WriteAsync(string address, string value, CancellationToken token = default)
{
IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, ThingsGatewayBitConverter);
byte[] data = transformParameter.GetBytes(value);
return WriteAsync(address, data, token);
}
/// <inheritdoc/>
public virtual string GetAddressDescription()
{
StringBuilder stringBuilder = new();
stringBuilder.AppendLine("通用格式");
stringBuilder.AppendLine("4字节转换格式");
stringBuilder.AppendLine("DATA=ABCD;");
stringBuilder.AppendLine("举例:");
stringBuilder.AppendLine("DATA=ABCD; 代表大端格式其中ABCD=>Big-Endian;BADC=>;Big-Endian Byte Swap;CDAB=>Little-Endian Byte Swap;DCBA=>Little-Endian");
stringBuilder.AppendLine("字符串长度:");
stringBuilder.AppendLine("LEN=1;");
stringBuilder.AppendLine("BCD格式");
stringBuilder.AppendLine("BCD=C8421;其中有C8421;C5421;C2421;C3;Gray");
stringBuilder.AppendLine("字符格式:");
stringBuilder.AppendLine("TEXT=UTF8;其中有UTF8;ASCII;Default;Unicode");
stringBuilder.AppendLine("");
return stringBuilder.ToString();
}
}

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