Compare commits

...

335 Commits

Author SHA1 Message Date
2248356998 qq.com
21215d0379 build: 10.11.109
feat: js刷新变量表数据
chore: 更新BootstrapBlazor.Chart依赖
2025-10-16 16:16:07 +08:00
Diego
7448183791 !78 feat: js刷新变量表数据
* 调整列宽
* feat: js刷新变量表数据
2025-10-16 08:09:29 +00:00
Diego
35edd7dc43 !77 update src/Admin/ThingsGateway.Common/Common/ConcurrentList.cs.
Merge pull request !77 from Sunny/v10
2025-10-16 06:14:04 +00:00
Sunny
bd178831e3 update src/Admin/ThingsGateway.Common/Common/ConcurrentList.cs.
修改 ConcurrentList.cs 一处错误代码

Signed-off-by: Sunny <yhuse@163.com>
2025-10-16 04:36:31 +00:00
2248356998 qq.com
fe9ec6ad10 fix: s7连接错误 2025-10-16 12:00:08 +08:00
2248356998 qq.com
6f9ec2e24b feat: 并发字典替换为 NonBlockingDictionary 类型 2025-10-15 17:40:33 +08:00
2248356998 qq.com
c0337e2b19 build: 10.11.105
feat: json添加AllowNamedFloatingPointLiterals
2025-10-15 11:16:18 +08:00
2248356998 qq.com
8a95f48f5a 适配 net10 rc2 2025-10-15 10:02:11 +08:00
2248356998 qq.com
14f3c31265 build: 10.11.102 2025-10-14 22:22:06 +08:00
2248356998 qq.com
1bad65378f build: 10.11.101 2025-10-14 11:46:26 +08:00
2248356998 qq.com
db3affc67e build: 10.11.100
feat(VariablePage): 优化UI性能
2025-10-14 10:18:09 +08:00
2248356998 qq.com
5ee8b50a92 build: 10.11.99 2025-10-13 22:11:05 +08:00
2248356998 qq.com
301beda2a2 build: 10.11.98 2025-10-13 21:10:39 +08:00
2248356998 qq.com
628b51a353 build: 10.11.97 2025-10-12 15:15:42 +08:00
2248356998 qq.com
f03445bc83 更新依赖 2025-10-12 00:25:57 +08:00
2248356998 qq.com
55a2ff5487 build: 10.11.96 2025-10-11 13:38:28 +08:00
2248356998 qq.com
0fef7dcf3b 更新依赖 2025-10-10 21:41:24 +08:00
2248356998 qq.com
19d9702606 fix: sqlite 批量更新 2025-10-10 14:19:43 +08:00
2248356998 qq.com
a8a9774932 fix: orm 序列化 2025-10-10 12:26:35 +08:00
2248356998 qq.com
aad0f0e8c3 fix: orm批量插入 2025-10-10 11:19:14 +08:00
2248356998 qq.com
e74eae50a7 build: 10.11.87 2025-10-09 20:58:16 +08:00
2248356998 qq.com
3b16d7019f feat: 优化orm 批量插入 2025-10-09 20:57:18 +08:00
2248356998 qq.com
3e038028c2 feat: 优化orm 批量插入 2025-10-09 19:19:18 +08:00
2248356998 qq.com
b1d8041f7e feat: 优化orm BulkCopy 2025-10-09 19:05:33 +08:00
2248356998 qq.com
53a98b26cd fix: 数据库插件保留天数逻辑错误 2025-10-09 08:52:02 +08:00
2248356998 qq.com
42c740fa1b 更新依赖 2025-10-07 22:25:38 +08:00
2248356998 qq.com
556819c90c 10.11.83 2025-09-30 15:25:15 +08:00
2248356998 qq.com
2522333a9c 更新依赖 2025-09-29 23:16:34 +08:00
2248356998 qq.com
bd4ce7c09b 适配net10 2025-09-29 17:47:24 +08:00
2248356998 qq.com
156ed88bd6 10.11.80 2025-09-29 17:09:12 +08:00
2248356998 qq.com
2416226eb0 10.11.78 2025-09-28 17:01:19 +08:00
2248356998 qq.com
976323a716 适配net10 2025-09-28 13:06:39 +08:00
2248356998 qq.com
3c9e397403 10.11.76 2025-09-28 00:33:56 +08:00
2248356998 qq.com
79406ad4a0 更新依赖 2025-09-27 23:59:38 +08:00
2248356998 qq.com
20c44f10ca 10.11.73 2025-09-27 20:18:56 +08:00
2248356998 qq.com
31d6b2a9e6 添加FastMapper 2025-09-26 15:24:37 +08:00
2248356998 qq.com
68e5a9c546 build: 10.11.71 2025-09-26 11:51:05 +08:00
2248356998 qq.com
b2ea9f99b9 适配net10 2025-09-26 10:58:57 +08:00
2248356998 qq.com
6d7d0e468a fix: opcua连接状态 2025-09-24 11:03:23 +08:00
2248356998 qq.com
ff1f632de2 build: 10.11.67 sqldb插件 2025-09-23 22:38:05 +08:00
2248356998 qq.com
fc3d7015ee sqldb插件 清理数据cron表达式错误 2025-09-23 22:36:45 +08:00
2248356998 qq.com
40c5acb522 调整UI 2025-09-23 13:41:20 +08:00
2248356998 qq.com
f9cc1cbb05 10.11.65 2025-09-23 12:02:17 +08:00
2248356998 qq.com
cf6e8b58f0 更新内存变量 2025-09-23 01:22:02 +08:00
2248356998 qq.com
615e3bb24c 修改 脚本调试项目 依赖项 2025-09-22 14:58:03 +08:00
2248356998 qq.com
4a7534b210 添加 脚本调试项目 2025-09-22 10:36:28 +08:00
2248356998 qq.com
58e099cb93 10.11.58 2025-09-21 20:53:50 +08:00
2248356998 qq.com
a94a9c953c 10.11.57 2025-09-19 14:32:34 +08:00
Diego
35e1ffa3e9 添加文件 2025-09-18 17:54:29 +08:00
Diego
4921642151 10.11.56 2025-09-18 17:15:57 +08:00
2248356998 qq.com
d71ee29da8 10.11.54 2025-09-17 20:50:41 +08:00
2248356998 qq.com
901aa2d59f 10.11.52 2025-09-17 11:07:45 +08:00
2248356998 qq.com
764957c014 10.11.51 2025-09-16 22:19:01 +08:00
Diego
0e3898218b 10.11.44 2025-09-16 19:13:19 +08:00
Diego
61f13cef3c 10.11.43 2025-09-15 17:35:49 +08:00
2248356998 qq.com
0b663d9e01 fix: modbusRtu 0x10 2025-09-14 11:18:41 +08:00
2248356998 qq.com
6c95c6209f 10.11.41 2025-09-13 14:22:13 +08:00
2248356998 qq.com
4d223d2622 10.11.40 2025-09-13 13:00:52 +08:00
yunqi
e8d7e91b64 !74 modify dlt645_2007 buildcmd
* modify dlt645_2007 buildcmd
2025-09-13 04:59:10 +00:00
2248356998 qq.com
8175f541ec 更新基准测试 2025-09-12 23:53:36 +08:00
2248356998 qq.com
0adbdb926b 更新基准测试 2025-09-12 23:49:18 +08:00
Diego
42adee9980 更新依赖 2025-09-11 22:22:39 +08:00
2248356998 qq.com
427a7404bc 更新UI 2025-09-11 19:05:14 +08:00
2248356998 qq.com
3658199e0a 10.11.37 2025-09-11 18:30:49 +08:00
Diego
82eedee50a 更新依赖 2025-09-11 18:12:40 +08:00
2248356998 qq.com
6a18fc3e06 10.11.36 2025-09-10 12:59:54 +08:00
2248356998 qq.com
c37e314ed6 更新依赖 2025-09-09 21:53:39 +08:00
2248356998 qq.com
a937a85d90 添加项目 2025-09-08 21:51:37 +08:00
2248356998 qq.com
35dd4ae9d3 更新依赖 2025-09-08 21:27:49 +08:00
2248356998 qq.com
0b829ac85c 10.11.33 2025-09-08 21:16:37 +08:00
2248356998 qq.com
aa247422d2 10.11.31 2025-09-08 17:56:19 +08:00
2248356998 qq.com
2e00e8c135 更新依赖 2025-09-08 08:46:52 +08:00
2248356998 qq.com
34dd2cf0a7 更新依赖 2025-09-07 21:52:41 +08:00
2248356998 qq.com
8404e20c5e build: 10.11.27
refactor: 更新依赖
2025-09-06 23:34:32 +08:00
2248356998 qq.com
662aa162e9 fix: sugar无实体curd 2025-09-05 13:16:18 +08:00
2248356998 qq.com
5927738c32 10.11.25 2025-09-05 11:30:33 +08:00
2248356998 qq.com
3c9f97a5c3 10.11.24 2025-09-04 22:39:21 +08:00
2248356998 qq.com
179ca0aa0e build: 10.11.23
feat: 增加部分demo
feat: 增加管理软件桌面端
2025-09-03 17:40:19 +08:00
Diego
fc09a52da1 build: 10.11.22
refactor:性能优化
2025-09-02 11:13:54 +08:00
Diego
5436b91c89 10.11.21 2025-09-01 18:40:12 +08:00
Diego
d1c46f51a6 添加api接口 2025-09-01 16:00:53 +08:00
Diego
4539d8d198 fix: 字符串反转功能失效 2025-09-01 11:52:03 +08:00
2248356998 qq.com
9ea9529a5f 10.11.19 2025-08-29 15:43:02 +08:00
2248356998 qq.com
4e6be23aac 10.11.17 2025-08-28 17:15:08 +08:00
2248356998 qq.com
2fabbd236b 10.11.16 2025-08-28 17:05:00 +08:00
2248356998 qq.com
163a66530e fix: dlt645校验和 2025-08-28 17:04:27 +08:00
2248356998 qq.com
29073a00c4 fix: dtu连接注册包解析错误 2025-08-28 15:35:43 +08:00
2248356998 qq.com
c6d4d1ecfa fix: dtu连接注册包解析错误 2025-08-28 15:20:00 +08:00
2248356998 qq.com
ba16889cad 10.11.14 2025-08-28 14:21:58 +08:00
2248356998 qq.com
5aaed35b0f 10.11.12 2025-08-28 01:42:59 +08:00
Diego
df067c91eb 10.11.12 2025-08-28 01:39:39 +08:00
2248356998 qq.com
2078b4a60b 补充忽略的文件 2025-08-26 08:54:09 +08:00
2248356998 qq.com
20a2e3ff8e 示例 2025-08-25 20:39:26 +08:00
Diego
61a973b1b5 !73 10.11.10 2025-08-25 12:21:12 +00:00
2248356998 qq.com
cbd72e2081 添加文本日志配置json文件 2025-08-22 16:17:25 +08:00
2248356998 qq.com
4e0377b20c 添加文本日志配置json文件 2025-08-22 16:15:54 +08:00
2248356998 qq.com
fd318d3cdc 移除警告项 2025-08-22 16:12:14 +08:00
2248356998 qq.com
515bdb9700 10.11.7 2025-08-21 23:35:12 +08:00
2248356998 qq.com
46c1780017 10.11.6 2025-08-21 22:40:53 +08:00
2248356998 qq.com
fe78a4c3ca 更新依赖库 2025-08-21 22:05:30 +08:00
2248356998 qq.com
2d7effadf9 10.11.4 2025-08-21 21:13:18 +08:00
Diego
346c560f8b !72 更新依赖项 2025-08-21 11:46:58 +00:00
Diego
8e3bd89f61 修改编译项 2025-08-18 17:30:34 +08:00
Diego
6da142d080 10.10.23 2025-08-18 17:03:40 +08:00
2248356998 qq.com
ff7d029e6f 更新依赖 2025-08-14 20:25:00 +08:00
2248356998 qq.com
21b4695683 10.10.19 2025-08-14 05:47:54 +08:00
Diego
02ad494a26 !71 适配远程客户端 2025-08-12 16:00:53 +00:00
2248356998 qq.com
280366e1b2 添加写优先选项,默认false 2025-08-09 13:07:08 +08:00
2248356998 qq.com
6660ce3e34 适配远程管理客户端 2025-08-08 18:01:24 +08:00
2248356998 qq.com
7499162c1a 适配远程管理客户端 2025-08-08 02:51:42 +08:00
2248356998 qq.com
40208a5cd6 适配远程网关管理客户端 2025-08-08 02:16:05 +08:00
2248356998 qq.com
fa347f4f68 修改报警事件时间字段,增加变量表报警类 2025-08-07 19:19:36 +08:00
Diego
d7df6fc605 10.10.12 2025-08-07 11:24:57 +08:00
2248356998 qq.com
eb4bb2fd48 10.10.11 2025-08-07 10:19:28 +08:00
2248356998 qq.com
faa9858974 10.10.11 2025-08-07 10:18:22 +08:00
2248356998 qq.com
1b3d2dda49 QuestDbRestAPI 2025-08-07 10:11:37 +08:00
Diego
a8a9453611 !70 fix: questdb restapi多实例 2025-08-07 01:58:32 +00:00
2248356998 qq.com
e84f42ce14 10.10.10 2025-08-06 21:42:46 +08:00
2248356998 qq.com
6f814cf6b8 更新questdb restapi启用字段 2025-08-06 21:42:23 +08:00
2248356998 qq.com
e36432e4e9 10.10.9 2025-08-06 19:33:30 +08:00
Diego
ebd71e807b !69 更新依赖 2025-08-06 11:25:31 +00:00
Diego
34000d8d7d !68 10.10.7 2025-08-05 09:22:11 +00:00
2248356998 qq.com
e785f6660c 10.10.5 2025-08-01 21:53:47 +08:00
Diego
831c611797 10.10.4 2025-08-01 17:30:37 +08:00
Diego
453817ef86 添加IAsyncDisposable 2025-08-01 16:36:27 +08:00
2248356998 qq.com
8ce0b981c1 no message 2025-08-01 12:55:01 +08:00
2248356998 qq.com
4e5c51b54c 2025-08-01 12:47:21 +08:00
2248356998 qq.com
3cc9d31f28 修改可用内存策略 2025-08-01 12:43:39 +08:00
2248356998 qq.com
10391f869b 支持相对路径创建sqlite 2025-07-31 23:41:58 +08:00
Diego
fba0723a6d 更新依赖 2025-07-31 18:39:46 +08:00
Diego
2db3f78f0c 添加 控制写操作与读操作的比率 的插件配置属性 2025-07-31 18:18:41 +08:00
Diego
badf61fe01 10.9.99 2025-07-31 16:25:47 +08:00
Diego
d74e0952dc 10.9.98 2025-07-31 15:57:33 +08:00
Diego
fb1699ce80 10.9.97 2025-07-31 14:06:47 +08:00
Diego
44adddbcd4 10.9.97 2025-07-31 13:56:49 +08:00
Diego
0eab889452 10.9.97 2025-07-31 13:56:34 +08:00
Diego
e14d39a459 添加 rpc写入 多写日志 2025-07-31 12:52:40 +08:00
2248356998 qq.com
7575264ede 添加变量初始化标记 2025-07-31 00:40:03 +08:00
2248356998 qq.com
3e1a077b96 添加变量初始化标记 2025-07-31 00:30:53 +08:00
2248356998 qq.com
a921cb8400 refactor: 连接时判断setup 2025-07-30 23:27:35 +08:00
2248356998 qq.com
4de7c31ed7 10.9.92 2025-07-30 22:00:23 +08:00
2248356998 qq.com
08326a2cfd fix: 单属性验证异常 2025-07-30 21:11:35 +08:00
Diego
e045de5acb !67 更新依赖 2025-07-30 10:01:20 +00:00
2248356998 qq.com
d3bef31aa6 10.9.70 2025-07-30 04:32:14 +08:00
2248356998 qq.com
347d4d6e5d 10.9.69 2025-07-25 22:21:34 +08:00
2248356998 qq.com
b006a066e1 10.9.69 2025-07-25 22:14:06 +08:00
Diego
e331bc5d3c 10.9.68 2025-07-25 20:30:48 +08:00
Diego
2e81806231 优化 2025-07-25 20:20:35 +08:00
Diego
37b48cf221 BindField 2025-07-24 20:03:53 +08:00
Diego
5997487434 修改参数 2025-07-24 19:46:30 +08:00
Diego
4ce843182f build: 10.9.67
feat: 报警恢复增加延时功能
refactor: 移除通讯任务时,设置设备、变量离线状态

fix: 罗克韦尔PLC通讯超时错误
2025-07-24 13:25:55 +08:00
2248356998 qq.com
a4aa000cf0 10.9.66 2025-07-23 19:11:39 +08:00
Diego
01aa6ca066 10.9.65 2025-07-23 16:34:02 +08:00
Diego
10a0280c4a 10.9.64 2025-07-22 15:55:49 +08:00
Diego
3b8b00c783 变量类验证提示 2025-07-22 11:03:48 +08:00
Diego
232ee5d5d4 10.9.63 2025-07-22 01:17:12 +08:00
2248356998 qq.com
605830edce 10.9.62 2025-07-20 21:52:59 +08:00
2248356998 qq.com
ca86441e05 orm性能优化 2025-07-20 19:04:25 +08:00
2248356998 qq.com
b30b876c5f 更新依赖 2025-07-20 10:44:20 +08:00
2248356998 qq.com
f95590b4cb 修正拼写错误 2025-07-19 22:36:13 +08:00
2248356998 qq.com
9921130406 更新依赖 2025-07-19 21:38:19 +08:00
2248356998 qq.com
5c47589ca1 更新依赖 2025-07-19 21:31:46 +08:00
2248356998 qq.com
048abfae2e 添加可空标识 2025-07-19 13:35:55 +08:00
Diego
8e35c16edf 恢复restart标记 2025-07-18 17:46:56 +08:00
Diego
143b751213 Revert "build: 10.9.42"
This reverts commit 2cafe745b9.
2025-07-18 14:41:19 +08:00
Diego
2cafe745b9 build: 10.9.42
feat: 优化读写调度
feat: rpc日志添加执行时间字段
2025-07-18 09:48:41 +08:00
Diego
210ac2c122 更新解决方案 2025-07-18 00:02:38 +08:00
Diego
4707ce6d58 新增RemoteWebApp项目 2025-07-17 23:41:11 +08:00
2248356998 qq.com
788a8b670d 10.9.40 2025-07-16 21:18:14 +08:00
2248356998 qq.com
5e4f0057e4 更新依赖库 2025-07-15 21:24:19 +08:00
2248356998 qq.com
2960c13ef1 更新依赖库 2025-07-15 03:56:46 +08:00
2248356998 qq.com
f11b7f7ab4 10.9.37 2025-07-13 22:47:14 +08:00
Diego
9bb9cd7419 10.9.35 2025-07-11 07:14:52 +08:00
Diego
ba008ef8ba 更新.NETVersion 2025-07-09 13:35:42 +08:00
Diego
28b533decf 10.9.34 2025-07-09 13:31:08 +08:00
Diego
9ee638c2f1 更新依赖包 2025-07-08 23:59:24 +08:00
Diego
d90fdbaf35 补充更新nuget包 2025-07-08 18:15:50 +08:00
Diego
b55e3db736 10.9.29 2025-07-08 17:55:37 +08:00
Diego
dbee8496cb opcuaServer支持decimal类型 2025-07-08 16:53:28 +08:00
Diego
044e78bea9 opcuaserver回退修改 2025-07-08 15:52:12 +08:00
Diego
fe79128d90 build: 10.9.26
fix: opcuaserver添加变量属性出现错误,已回退
refactor: 网关监控树节点保持展开状态
refactor: cache插件类日志输出
2025-07-08 14:33:27 +08:00
Diego
34120da008 ToDecimal方法添加异步捕获,防止double类型值超限 2025-07-08 11:21:06 +08:00
Diego
4c62bb0b21 10.9.25 2025-07-08 10:19:01 +08:00
Diego
46b16279c7 build: 10.9.24 2025-07-08 09:41:45 +08:00
2248356998 qq.com
0779efc5dd refactor: 冗余服务代码修正 2025-07-08 02:38:05 +08:00
Diego
8acdb780e8 nuget本地源创建文件夹 2025-07-07 19:07:08 +08:00
Diego
2e310b919e 添加Keys文件夹 2025-07-07 16:54:58 +08:00
Diego
4155c07269 10.9.23 2025-07-07 15:20:39 +08:00
Diego
32fa833736 build: 10.9.22 2025-07-07 15:10:06 +08:00
Diego
2258f08555 build: 10.9.19 2025-07-07 13:44:19 +08:00
2248356998 qq.com
d90b32f165 refactor: 调整冗余服务代码 2025-07-07 00:15:46 +08:00
2248356998 qq.com
1492377322 refactor:调整部分插件父类 2025-07-06 01:33:15 +08:00
Diego
32eefbf545 build: 10.9.18
feat: 网关监控树节点添加右侧常用操作按钮
2025-07-04 13:54:35 +08:00
Diego
a825ca5f6f 网关监控树节点添加右侧常用操作按钮 2025-07-04 12:50:07 +08:00
Diego
e270b0c4f6 no message 2025-07-04 09:01:31 +08:00
Diego
6ae44ccf58 10.9.17 2025-07-04 08:36:37 +08:00
Diego
1aa0df6339 feat: 支持变量低内存导出 2025-07-03 23:24:56 +08:00
Diego
62c3693dbe 导出通道和设备支持IEnumerable低内存 2025-07-03 19:34:51 +08:00
Diego
e4e503c97b build: 添加nuget.config,自定义nuget源,方便本地调试自行编译 2025-07-03 12:43:51 +08:00
2248356998 qq.com
5ec1ee7627 fix: 上次更新导致大小端配置映射错误 2025-07-02 21:51:00 +08:00
Diego
79789388fc fix: opcuaserver动态刷新变量节点会导致新建的动态节点无法刷新订阅 2025-07-02 19:11:11 +08:00
Diego
2c4194ee18 refactor: opcua某些不存在的节点ID不再影响整体订阅,只出现日志提示 2025-07-02 15:09:52 +08:00
Diego
1b2be585af fix: 报警分析错误设置循环 2025-07-02 14:26:25 +08:00
Diego
83736647e7 feat: S7PLC增加WString支持 2025-07-02 12:49:55 +08:00
Diego
b06405717d build: 10.9.9
fix: timerx 池 max值取消
feat: mqttrpc支持脚本
2025-07-02 10:03:50 +08:00
2248356998 qq.com
298a1f2ed4 更新docker文件 2025-07-02 07:32:23 +08:00
Diego
74a47a1983 build: 10.9.8
支持redis缓存
2025-07-01 17:55:03 +08:00
Diego
a48a42abe4 modbusslave 异常捕获 2025-07-01 10:51:10 +08:00
Diego
feb1d0a3c5 feat: 增加后台服务生命周期识别 2025-06-30 10:59:18 +08:00
Diego
92bca824e6 10.9.6 2025-06-29 22:19:53 +08:00
2248356998 qq.com
025c699517 feat: s7增加请求id识别 2025-06-29 22:06:53 +08:00
2248356998 qq.com
53f8fbe4b1 refactor: 变量排序导出 2025-06-28 21:43:06 +08:00
2248356998 qq.com
77bfabc41d feat: 改用Mapperly源生成,代替Mapster 2025-06-28 00:00:43 +08:00
Diego
6427ee6ee0 refactor: 降低sqlite依赖 2025-06-27 14:44:00 +08:00
Diego
4c95997d62 build: 10.9.2
fix: taos connection dispose
refactor: opcua AddSubscriptionAsync 添加延时和重试
2025-06-27 11:16:58 +08:00
Diego
1d82cea40d build: 10.9.1
fix: 任务空错误异常
feat: 数据库插件增加字符串变量表和数值变量表两种情况
2025-06-27 03:02:03 +08:00
Diego
e78799424c 添加日志输出筛选 2025-06-25 17:09:05 +08:00
Diego
1000c8d38f docs: 更新采集插件开发文档 2025-06-25 14:22:58 +08:00
Diego
b2589fc634 build: 10.8.24
fix: 变量离线后再次上线,如果值不变,会导致在线状态不刷新
fix: s7离线恢复时,可能触发多次协议握手导致异常
2025-06-25 11:19:06 +08:00
Diego
c80e57a4e8 build: 10.8.24
fix: 变量离线后再次上线,如果值不变,会导致在线状态不刷新
fix: s7离线恢复时,可能触发多次协议握手导致异常
2025-06-25 11:17:04 +08:00
Diego
6510c3e289 feat: 增加一个变量读写表达式常用转换的友好编辑界面 2025-06-24 16:14:35 +08:00
Diego
920e407d05 恢复规则引擎脚本接口 2025-06-24 10:52:00 +08:00
Diego
7314c8901d fix: 用户编辑框初始刷新职位 2025-06-24 09:15:33 +08:00
Diego
4e9f02b48c 更新依赖包 2025-06-24 09:03:34 +08:00
2248356998 qq.com
9ae7602cb4 配置最大连接数 2025-06-24 00:09:07 +08:00
2248356998 qq.com
aa8aa36aef build: 10.8.19
fix: s7 复用地址对象导致读取异常
feat: 规则引擎node添加内部异常捕获
feat: 变量增加属性: 写入后再次读取检查值是否一致
2025-06-23 21:21:27 +08:00
2248356998 qq.com
0174f7c6f2 更新依赖 2025-06-22 23:05:12 +08:00
2248356998 qq.com
df9e7d6ff1 10.8.17 2025-06-22 21:11:37 +08:00
Diego
b40ca920d3 fix: 变量自动刷新运行态 2025-06-20 16:58:45 +08:00
Diego
5a4b0a0e93 修改插件过期提示 2025-06-20 14:43:13 +08:00
Diego
a879edd68b 10.8.12 2025-06-20 13:52:42 +08:00
Diego
62e0a6ee9d gitee登录按钮隐藏 2025-06-19 17:04:22 +08:00
Diego
765e5564d4 10.8.10 2025-06-19 16:56:04 +08:00
Diego
10eecac19b feat: 优化设备状态逻辑 2025-06-19 11:41:43 +08:00
Diego
59241b8faa fix: opcua插件订阅检查失效 2025-06-19 10:42:54 +08:00
Diego
52b3097f04 10.8.7 2025-06-18 22:04:48 +08:00
Diego
d922296b70 feat: 重构插件任务 2025-06-18 17:11:57 +08:00
Diego
aec91da28b 10.8.2
feat: 优化闭包导致的状态机内存占用,高并发时内存显著下降
fix(sqldb): 定时上传模式时,实时表时效
fix(taos): 初始化失败
2025-06-17 17:09:05 +08:00
Diego
013ff394be 10.8.1 2025-06-16 18:25:55 +08:00
Diego
081e07473d 10.8.0 2025-06-16 18:12:28 +08:00
Diego
d33d900592 增加github oauth登录 2025-06-15 00:17:25 +08:00
Diego
29365c4ef9 修改脚本测试方法 2025-06-14 11:56:31 +08:00
Diego
17a6189089 style: 更新SysResourcePage页面样式 2025-06-13 16:03:28 +08:00
Diego
003b8a3763 10.7.56 2025-06-13 13:35:38 +08:00
Diego
1c7f8b5cab 10.7.55 2025-06-13 09:14:22 +08:00
Diego
b7ff9ffca2 更新解决方案 2025-06-13 09:06:42 +08:00
Diego
9bba9bda76 10.7.54 2025-06-12 23:54:10 +08:00
Diego
ec2fcc75d3 合并代码 2025-06-12 20:21:50 +08:00
Diego
57a4038577 pwa安装提示优化 2025-06-12 10:20:52 +08:00
Diego
b78a76e60f 增加采集设备写入数据的控制台文本日志 2025-06-11 23:49:37 +08:00
Diego
28bd751d44 增加tab右键菜单 2025-06-11 22:57:00 +08:00
Diego
dcba7b9810 10.7.50 2025-06-11 21:50:46 +08:00
Diego
7f1a741ce6 优化sql实时表执行性能 2025-06-11 21:39:14 +08:00
Diego
ca2b17d433 10.7.47 2025-06-11 17:08:19 +08:00
Diego
af589eac10 更新依赖 2025-06-11 15:05:03 +08:00
Diego
573670f1f5 10.7.45 2025-06-11 10:26:48 +08:00
Diego
f3ec85a03d 更新测试 2025-06-10 16:26:14 +08:00
Diego
c94308454f build: 10.7.42
feat:更改后台服务多语言处理方式
2025-06-10 15:49:47 +08:00
Diego
c9c2b2b69d 10.7.39 2025-06-09 16:11:37 +08:00
Diego
40574b776f feat(TDengine): 支持websocket连接,无需安装sdk 2025-06-09 12:38:30 +08:00
Diego
f426c1533d fix: taos插件 not found dll错误 2025-06-09 10:07:58 +08:00
Diego
4a3b5163f1 更新依赖 2025-06-09 09:29:41 +08:00
2248356998 qq.com
3a0946d357 优化单例db对象获取 2025-06-08 21:09:07 +08:00
Diego
9dd21c644c 10.7.35 2025-06-08 15:48:13 +08:00
2248356998 qq.com
6713190a23 日志写入器释放 2025-06-08 15:47:25 +08:00
Diego
9f24533425 10.7.34 2025-06-08 15:38:41 +08:00
2248356998 qq.com
ed1d9374eb 2025-06-08 01:22:41 +08:00
Diego
f14d27129e 10.7.33 2025-06-07 22:30:27 +08:00
2248356998 qq.com
5b04f02fbe Merge branch 'v10' of https://gitee.com/ThingsGateway/ThingsGateway into v10 2025-06-07 22:26:32 +08:00
2248356998 qq.com
227080e332 ddp协议清理旧连接逻辑错误 2025-06-07 22:26:18 +08:00
Diego
1e87482a49 10.7.32 2025-06-07 15:25:38 +08:00
2248356998 qq.com
054d31c3ea 添加自定义插件 2025-06-07 15:14:54 +08:00
Diego
3a068a7b03 更新解决方案 2025-06-07 12:22:05 +08:00
Diego
b12e923c99 10.7.31 2025-06-06 23:47:07 +08:00
Diego
ab33eed8d3 10.7.22 2025-06-06 21:13:58 +08:00
Diego
d930a9a8eb 修改演示站登录ui 2025-06-05 08:40:25 +08:00
Diego
af381fce12 插件调试增加双击显示 2025-06-04 09:45:02 +08:00
Diego
b64ac0539e 更新依赖 2025-06-03 22:27:28 +08:00
Diego
541c60b363 10.7.17 2025-06-02 19:45:56 +08:00
Diego
824e95f7cb 10.7.16 2025-06-02 19:44:30 +08:00
youthalan
38f7850196 !66 修复缺少引用编译错误
* 修复缺少引用编译错误
2025-05-31 06:35:47 +00:00
Diego
bef9de88e2 oauth增加scope参数 2025-05-31 02:17:09 +08:00
Diego
48cd5e7c7f 增加演示站的gitee授权 2025-05-31 00:43:43 +08:00
Diego
3b44fda51c 修复hybrid程序 2025-05-30 13:48:03 +08:00
Diego
dbfc9a5bb4 更新授权显示 2025-05-30 11:21:45 +08:00
Diego
1b758aa41a 10.6.37 2025-05-30 10:15:58 +08:00
Diego
43bdc70899 优化文本日志读取 2025-05-30 10:02:51 +08:00
Diego
fadda000a6 添加sqlsugar类 2025-05-29 22:18:33 +08:00
Diego
45a8c91a5a 优化UI刷新 2025-05-29 17:05:06 +08:00
Diego
8e938f18be 去除不必要的控制台日志输出 2025-05-28 16:52:51 +08:00
Diego
ab1b364c54 fix: 同步插件反写空错误 2025-05-28 12:30:16 +08:00
Diego
5ec65b2fb0 10.6.29 2025-05-28 10:47:31 +08:00
Diego
926eced724 10.6.28 2025-05-27 17:20:05 +08:00
Diego
f7f8802272 2025-05-27 13:19:39 +08:00
Diego
c6910dff02 update src/Gateway/ThingsGateway.Gateway.Application/ThingsGateway.Gateway.Application.csproj.
Signed-off-by: Diego <2248356998@qq.com>
2025-05-27 01:37:29 +00:00
Diego
ad299d0dbb 2025-05-27 08:52:06 +08:00
2248356998 qq.com
8b124d1050 日志统计查询性能增强 2025-05-27 00:03:30 +08:00
Diego
ff41080dbd 更新授权类 2025-05-26 19:51:21 +08:00
Diego
0e28606e3d 10.6.23 2025-05-26 18:43:42 +08:00
Diego
6a025ceee5 序列化配置增加nan的情况 2025-05-26 17:41:13 +08:00
Diego
6b2e53d6dc 更新配置 2025-05-26 09:17:59 +08:00
2248356998 qq.com
b989aa5561 10.6.21 2025-05-26 00:05:16 +08:00
2248356998 qq.com
f5b0b7ebd2 fix: s7多写 2025-05-24 14:22:58 +08:00
2248356998 qq.com
16881ae076 fix: s7多写 2025-05-24 14:18:58 +08:00
2248356998 qq.com
af04112656 10.6.20
fix: s7多写
fix: opcuaserver写入数组
2025-05-24 14:11:10 +08:00
Diego
a2863112dc 2025-05-23 15:47:41 +08:00
Diego
f531e4dfc5 10.6.19 2025-05-23 13:16:57 +08:00
Diego
8db9b32ba7 10.6.18 2025-05-23 12:55:21 +08:00
Diego
dd5691cbef 更新依赖 2025-05-22 14:40:56 +08:00
Diego
de48b32af3 优化硬件信息曲线数据 2025-05-21 17:10:28 +08:00
Diego
600b5042a1 更新首页 2025-05-21 16:51:40 +08:00
Diego
aac77029da 修改数据保护密钥持久化位置 2025-05-21 13:15:39 +08:00
Diego
e50205f557 导出excel不再按设备名称和变量名称排序 2025-05-21 10:10:59 +08:00
Diego
e227411d1f fix: taos插件查询错误 2025-05-21 09:34:31 +08:00
2248356998 qq.com
2de0ed793f 调整json序列化内容 2025-05-20 23:21:58 +08:00
Diego
cb0276f273 fix: opcuamaster插件属性UI未正确显示 2025-05-20 15:20:14 +08:00
Diego
562b3f17c9 10.6.11 2025-05-19 23:16:49 +08:00
Diego
0f78f81c1c 10.6.7 2025-05-19 18:43:03 +08:00
Diego
6594937d0a 网关冗余备用站和同步插件支持 变量写入 2025-05-19 18:01:23 +08:00
Diego
64bc6084be 更新sln 2025-05-19 12:23:41 +08:00
Diego
20434d5bd2 10.6.5 2025-05-19 12:13:11 +08:00
Diego
9ecc9380e6 同步更改 2025-05-19 12:12:44 +08:00
Diego
a57c55080b 增加数据同步插件 2025-05-19 12:10:11 +08:00
2248356998 qq.com
b8ca06c6be fix: hybrid运行切换语言错误 2025-05-17 16:32:36 +08:00
Diego
5aff6461a1 fix: opcuaServer 业务设备刷新变量时可能导致内存泄露 2025-05-16 19:26:33 +08:00
Diego
6cf53fefec 优化内存占用 2025-05-16 18:00:36 +08:00
Diego
45132f3503 更新依赖 2025-05-16 10:51:44 +08:00
Diego
f1e78a0e8a 恢复配置json 2025-05-15 12:17:43 +08:00
Diego
0bf28ec275 更新语言资源 2025-05-15 12:17:09 +08:00
Diego
41f8412c97 支持达梦数据库 2025-05-15 12:15:32 +08:00
Diego
c535974362 更新规则引擎示例 2025-05-15 10:44:12 +08:00
Diego
1860c5f215 build:10.6.1 2025-05-15 09:12:33 +08:00
Diego
6d778b2d39 增加initDatabase配置项 2025-05-15 09:08:08 +08:00
Diego
f48b99c259 更新示例 2025-05-14 21:12:51 +08:00
Diego
3c73b93051 更新依赖 2025-05-14 18:52:19 +08:00
2580 changed files with 217048 additions and 35308 deletions

4
.gitignore vendored
View File

@@ -364,5 +364,5 @@ FodyWeavers.xsd
/src/*Pro*/
/src/*Pro*
/src/*pro*
/src/*pro*/
/src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json
/src/.idea/

View File

@@ -85,7 +85,7 @@
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
as of the date such litigation is field.
4. Cachetribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without

View File

@@ -126,35 +126,8 @@ dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
dotnet_diagnostic.CA2208.severity = none
dotnet_diagnostic.CA2008.severity = none
dotnet_diagnostic.CA1812.severity = none
dotnet_diagnostic.CA1508.severity = none
dotnet_diagnostic.CA1512.severity = none
dotnet_diagnostic.CA1513.severity = none
dotnet_diagnostic.CA1810.severity = none
dotnet_diagnostic.CA1814.severity = none
dotnet_diagnostic.CA1815.severity = none
dotnet_diagnostic.CA1835.severity = none
dotnet_diagnostic.CA1819.severity = none
dotnet_diagnostic.CA1823.severity = none
dotnet_diagnostic.CA2002.severity = none
dotnet_diagnostic.CA5350.severity = none
dotnet_diagnostic.CA5351.severity = none
dotnet_diagnostic.CA5358.severity = none
dotnet_diagnostic.CA5384.severity = none
dotnet_diagnostic.CA5392.severity = none
dotnet_diagnostic.CA1805.severity = none
dotnet_diagnostic.CA1851.severity = none
dotnet_diagnostic.CA1510.severity = none
dotnet_diagnostic.CA5401.severity = none
dotnet_diagnostic.CA2022.severity = none
dotnet_diagnostic.CA1848.severity = none
dotnet_diagnostic.CA2000.severity = none
dotnet_diagnostic.CA5394.severity = none
dotnet_diagnostic.CA3003.severity = none
dotnet_diagnostic.CA1515.severity = none
dotnet_diagnostic.CA1849.severity = none
dotnet_diagnostic.RCS1146.severity = warning
dotnet_diagnostic.RCS1059.severity = none
dotnet_diagnostic.RCS1138.severity = suggestion
dotnet_code_quality.CA1822.api_surface = private, internal

View File

@@ -0,0 +1,6 @@
## Release 1.0
### New Rules
Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
TG0001 | Conflict | Error | SetParametersAsyncGenerator

View File

@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\PackNuget.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;</TargetFrameworks>
<Version>$(SourceGeneratorVersion)</Version>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<NoPackageAnalysis>true</NoPackageAnalysis>
<SignAssembly>false</SignAssembly>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<IncludeBuildOutput>false</IncludeBuildOutput>
<!-- 避免 DLL 被打包到 lib/ -->
<EnableSourceGenerator>true</EnableSourceGenerator>
<!-- 可选 -->
</PropertyGroup>
<ItemGroup>
<None Include="$(OutputPath)\$(TargetFileName)" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" PrivateAssets="all" Private="false" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,440 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Text;
namespace Microsoft.AspNetCore.Components;
[Generator]
public sealed partial class SetParametersAsyncGenerator : IIncrementalGenerator
{
private const string m_DoNotGenerateSetParametersAsyncAttribute = """
using System;
namespace Microsoft.AspNetCore.Components
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
internal sealed class DoNotGenerateSetParametersAsyncAttribute : Attribute { }
}
""";
private const string m_GenerateSetParametersAsyncAttribute = """
using System;
namespace Microsoft.AspNetCore.Components
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
internal sealed class GenerateSetParametersAsyncAttribute : Attribute
{
public bool RequireExactMatch { get; set; }
}
}
""";
private const string m_GlobalGenerateSetParametersAsyncAttribute = """
using System;
namespace Microsoft.AspNetCore.Components
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
internal sealed class GlobalGenerateSetParametersAsyncAttribute : Attribute
{
public bool Enable { get; }
public GlobalGenerateSetParametersAsyncAttribute(bool enable = true) { Enable = enable; }
}
}
""";
private static readonly DiagnosticDescriptor ParameterNameConflict = new DiagnosticDescriptor(
id: "TG0001",
title: "Parameter name conflict",
messageFormat: "Parameter names are case insensitive. {0} conflicts with {1}.",
category: "Conflict",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "Parameter names must be case insensitive to be usable in routes. Rename the parameter to not be in conflict with other parameters.");
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 注入 attribute 源码
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddSource("DoNotGenerateSetParametersAsyncAttribute.g.cs", SourceText.From(m_DoNotGenerateSetParametersAsyncAttribute, Encoding.UTF8));
ctx.AddSource("GenerateSetParametersAsyncAttribute.g.cs", SourceText.From(m_GenerateSetParametersAsyncAttribute, Encoding.UTF8));
ctx.AddSource("GlobalGenerateSetParametersAsyncAttribute.g.cs", SourceText.From(m_GlobalGenerateSetParametersAsyncAttribute, Encoding.UTF8));
});
// 筛选 ClassDeclarationSyntax
var classDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (node, _) => node is ClassDeclarationSyntax,
transform: static (ctx, _) => (ClassDeclarationSyntax)ctx.Node)
.Where(static c => c is not null);
// 合并 Compilation
var compilationProvider = context.CompilationProvider;
var candidateClasses = classDeclarations.Combine(compilationProvider);
context.RegisterSourceOutput(candidateClasses, static (spc, tuple) =>
{
var (classDeclaration, compilation) = tuple;
Execute(spc, compilation, classDeclaration);
});
}
private static void Execute(SourceProductionContext context, Compilation compilation, ClassDeclarationSyntax classDeclaration)
{
var model = compilation.GetSemanticModel(classDeclaration.SyntaxTree);
var classSymbol = model.GetDeclaredSymbol(classDeclaration);
if (classSymbol is null || classSymbol.Name == "_Imports")
return;
var positiveAttr = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.GenerateSetParametersAsyncAttribute");
var negativeAttr = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.DoNotGenerateSetParametersAsyncAttribute");
if (classSymbol.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, negativeAttr)))
return;
if (!IsPartial(classSymbol) || !IsComponent(classDeclaration, classSymbol, compilation))
return;
var globalEnable = compilation.Assembly.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.GlobalGenerateSetParametersAsyncAttribute")
?.ConstructorArguments.FirstOrDefault().Value as bool? ?? false;
var hasPositive = classSymbol.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, positiveAttr));
if (!globalEnable && !hasPositive)
return;
GenerateSetParametersAsyncMethod(context, classSymbol);
}
private static void GenerateSetParametersAsyncMethod(SourceProductionContext context, INamedTypeSymbol class_symbol)
{
var force_exact_match = class_symbol.GetAttributes().Any(a => a.NamedArguments.Any(na => na.Key == "RequireExactMatch" && na.Value.Value is bool v && v));
var namespaceName = class_symbol.ContainingNamespace.ToDisplayString();
var type_kind = class_symbol.TypeKind switch { TypeKind.Class => "class", TypeKind.Interface => "interface", _ => "struct" };
var type_parameters = string.Join(", ", class_symbol.TypeArguments.Select(t => t.Name));
type_parameters = string.IsNullOrEmpty(type_parameters) ? type_parameters : "<" + type_parameters + ">";
context.AddCode(class_symbol.ToDisplayString() + "_override.cs", $@"
using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using System.Threading.Tasks;
#pragma warning disable CA1849
#pragma warning disable CA2007
#pragma warning disable CS0162
#pragma warning disable CS8632
namespace {namespaceName}
{{
public partial class {class_symbol.Name}{type_parameters}
{{
private bool _initialized;
/// <summary>
/// <inheritdoc/>
/// </summary>
public override Task SetParametersAsync(ParameterView parameters)
{{
Dictionary<string,object?> parameterValues = new();
foreach (var parameter in parameters)
{{
if(BlazorImplementation__WriteSingleParameter(parameter.Name, parameter.Value)==false)
{{
// 如果没有处理参数,则添加到参数列表中
parameterValues.Add(parameter.Name, parameter.Value);
}}
}}
if(parameterValues.Count > 0)
{{
parameters.SetParameterProperties(this);
}}
if (!_initialized)
{{
_initialized = true;
return RunInitAndSetParametersAsync();
}}
else
{{
return CallOnParametersSetAsync();
}}
}}
// We do not want the debugger to consider NavigationExceptions caught by this method as user-unhandled.
#if NET9_0_OR_GREATER
[System.Diagnostics.DebuggerDisableUserUnhandledExceptions]
#endif
private async Task RunInitAndSetParametersAsync()
{{
Task task;
try
{{
OnInitialized();
task = OnInitializedAsync();
}}
catch (Exception ex) when (ex is not NavigationException)
{{
throw;
}}
if (task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled)
{{
// Call state has changed here so that we render after the sync part of OnInitAsync has run
// and wait for it to finish before we continue. If no async work has been done yet, we want
// to defer calling StateHasChanged up until the first bit of async code happens or until
// the end. Additionally, we want to avoid calling StateHasChanged if no
// async work is to be performed.
StateHasChanged();
try
{{
await task;
}}
catch // avoiding exception filters for AOT runtime support
{{
// Ignore exceptions from task cancellations.
// Awaiting a canceled task may produce either an OperationCanceledException (if produced as a consequence of
// CancellationToken.ThrowIfCancellationRequested()) or a TaskCanceledException (produced as a consequence of awaiting Task.FromCanceled).
// It's much easier to check the state of the Task (i.e. Task.IsCanceled) rather than catch two distinct exceptions.
if (!task.IsCanceled)
{{
throw;
}}
}}
// Don't call StateHasChanged here. CallOnParametersSetAsync should handle that for us.
}}
await CallOnParametersSetAsync();
}}
// We do not want the debugger to consider NavigationExceptions caught by this method as user-unhandled.
#if NET9_0_OR_GREATER
[System.Diagnostics.DebuggerDisableUserUnhandledExceptions]
#endif
private Task CallOnParametersSetAsync()
{{
Task task;
try
{{
OnParametersSet();
task = OnParametersSetAsync();
}}
catch (Exception ex) when (ex is not NavigationException)
{{
#if NET9_0_OR_GREATER
System.Diagnostics.Debugger.BreakForUserUnhandledException(ex);
#endif
throw;
}}
// If no async work is to be performed, i.e. the task has already ran to completion
// or was canceled by the time we got to inspect it, avoid going async and re-invoking
// StateHasChanged at the culmination of the async work.
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
task.Status != TaskStatus.Canceled;
// We always call StateHasChanged here as we want to trigger a rerender after OnParametersSet and
// the synchronous part of OnParametersSetAsync has run.
StateHasChanged();
return shouldAwaitTask ?
CallStateHasChangedOnAsyncCompletion(task) :
Task.CompletedTask;
}}
// We do not want the debugger to stop more than once per user-unhandled exception.
#if NET9_0_OR_GREATER
[System.Diagnostics.DebuggerDisableUserUnhandledExceptions]
#endif
private async Task CallStateHasChangedOnAsyncCompletion(Task task)
{{
try
{{
await task;
}}
catch // avoiding exception filters for AOT runtime support
{{
// Ignore exceptions from task cancellations, but don't bother issuing a state change.
if (task.IsCanceled)
{{
return;
}}
throw;
}}
StateHasChanged();
}}
}}
}}
#pragma warning restore CS8632
#pragma warning restore CS0162
#pragma warning restore CA2007
#pragma warning restore CA1849
");
var bases = class_symbol.GetTypeHierarchy().Where(t => !SymbolEqualityComparer.Default.Equals(t, class_symbol));
var members = class_symbol.GetMembers() // members of the type itself
.Concat(bases.SelectMany(t => t.GetMembers().Where(m => m.DeclaredAccessibility != Accessibility.Private))) // plus accessible members of any base
.Distinct(SymbolEqualityComparer.Default);
var property_symbols = members.OfType<IPropertySymbol>();
var writable_property_symbols = property_symbols.Where(ps =>
!ps.IsReadOnly || ps.GetAttributes().Any(a =>
a.AttributeClass?.Name is "CascadingParameter" or "CascadingParameterAttribute")
);
var parameter_symbols = writable_property_symbols
.Where(ps => ps.GetAttributes().Any(a => false
|| a.AttributeClass.Name == "Parameter"
|| a.AttributeClass.Name == "ParameterAttribute"
|| a.AttributeClass.Name == "CascadingParameter"
|| a.AttributeClass.Name == "CascadingParameterAttribute"
));
var name_conflicts = parameter_symbols.GroupBy(ps => ps.Name.ToLowerInvariant()).Where(g => g.Count() > 1);
foreach (var conflict in name_conflicts)
{
var key = conflict.Key;
var conflicting_parameters = conflict.ToList();
foreach (var parameter in conflicting_parameters)
{
var this_name = parameter.Name;
var conflicting_name = conflicting_parameters.Select(p => p.Name).FirstOrDefault(n => n != this_name);
foreach (var location in parameter.Locations)
{
context.ReportDiagnostic(Diagnostic.Create(ParameterNameConflict, location, this_name, conflicting_name));
}
}
}
var all = parameter_symbols.ToList();
var catch_all_parameter = parameter_symbols.FirstOrDefault(p =>
{
var parameter_attr = p.GetAttributes().FirstOrDefault(a => a.AttributeClass!.Name.StartsWith("Parameter"));
return parameter_attr?.NamedArguments.Any(n => n.Key == "CaptureUnmatchedValues" && n.Value.Value is bool v && v) == true;
});
var lower_case_match_cases = parameter_symbols.Except(new[] { catch_all_parameter }).Select(p => $"case \"{p.Name.ToLowerInvariant()}\": this.{p.Name} = ({p.Type.ToDisplayString()}) value; break;");
var lower_case_match_default = catch_all_parameter == null ? @"default: {return false;}" : $@"
default:
{{
this.{catch_all_parameter.Name} ??= new System.Collections.Generic.Dictionary<string, object>();
var writable_dict = this.{catch_all_parameter.Name};
if (!writable_dict.TryAdd(name, value))
{{
writable_dict[name] = value;
}}
break;
}}";
var exact_match_cases = parameter_symbols.Except(new[] { catch_all_parameter }).Select(p => $"case \"{p!.Name}\": this.{p.Name} = ({p.Type.ToDisplayString()}) value; break;");
string exact_match_default;
if (force_exact_match)
{
if (catch_all_parameter == null) // exact matches are forced, and we do not have a catch-all parameter, therefore we need to throw on unmatched parameter
{
exact_match_default = @"default: { return false;";
}
else // exact matches are forced, and we DO have a catch-all parameter, therefore we simply add that unmatched parameter to the dictionary
{
exact_match_default = $@"
default:
{{
this.{catch_all_parameter.Name} ??= new System.Collections.Generic.Dictionary<string, object>();
var writable_dict = this.{catch_all_parameter.Name};
if (!writable_dict.TryAdd(name, value))
{{
writable_dict[name] = value;
}}
break;
}}";
}
}
else
{
// exact matches are not forced, so if there is no exact match, we fall back to compare it in lower case
exact_match_default = $@"
default:
{{
switch (name.ToLowerInvariant())
{{
{string.Join("\n", lower_case_match_cases)}
{lower_case_match_default}
}}
break;
}}
";
}
context.AddCode(class_symbol.ToDisplayString() + "_implementation.cs", $@"
using System;
#pragma warning disable CS0162
#pragma warning disable CS0618
#pragma warning disable CS8632
namespace {namespaceName}
{{
public partial class {class_symbol.Name}{type_parameters}
{{
private bool BlazorImplementation__WriteSingleParameter(string name, object value)
{{
if(name != ""Body"")
{{
switch (name)
{{
{string.Join("\n", exact_match_cases)}
{exact_match_default}
}}
return true;
}}
return false;
}}
}}
}}
#pragma warning restore CS8632
#pragma warning restore CS0618
#pragma warning restore CS0162");
}
private static bool IsPartial(INamedTypeSymbol symbol)
{
return symbol.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
.OfType<ClassDeclarationSyntax>()
.Any(c => c.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)));
}
private static bool IsComponent(ClassDeclarationSyntax classDeclaration, INamedTypeSymbol symbol, Compilation compilation)
{
if (HasUserDefinedSetParametersAsync(symbol))
return false;
if (classDeclaration.SyntaxTree.FilePath.EndsWith(".razor") || classDeclaration.SyntaxTree.FilePath.EndsWith(".razor.cs"))
return true;
var iComponent = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.IComponent");
var componentBase = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.ComponentBase");
if (iComponent == null || componentBase == null)
return false;
return symbol.AllInterfaces.Contains(iComponent) || SymbolEqualityComparer.Default.Equals(symbol.BaseType, componentBase);
}
private static bool HasUserDefinedSetParametersAsync(INamedTypeSymbol classSymbol)
{
return classSymbol
.GetMembers("SetParametersAsync")
.OfType<IMethodSymbol>()
.Any(m =>
m.Parameters.Length == 1 &&
m.Parameters[0].Type.ToDisplayString() == "Microsoft.AspNetCore.Components.ParameterView" &&
m.DeclaredAccessibility == Accessibility.Public &&
!m.IsStatic);
}
}

View File

@@ -0,0 +1,15 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
namespace Microsoft.AspNetCore.Components
{
internal static class SourceGeneratorContextExtension
{
public static void AddCode(this SourceProductionContext context, string hint_name, string code)
{
context.AddSource(hint_name.Replace("<", "_").Replace(">", "_"), SourceText.From(code, Encoding.UTF8));
}
}
}

View File

@@ -0,0 +1,19 @@
using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Components
{
public static class TypeSymbolExtension
{
public static IEnumerable<INamedTypeSymbol> GetTypeHierarchy(this INamedTypeSymbol symbol)
{
yield return symbol;
if (symbol.BaseType != null)
{
foreach (var type in GetTypeHierarchy(symbol.BaseType))
{
yield return type;
}
}
}
}
}

View File

@@ -0,0 +1,49 @@
param($installPath, $toolsPath, $package, $project)
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve
foreach($analyzersPath in $analyzersPaths)
{
# Install the language agnostic analyzers.
if (Test-Path $analyzersPath)
{
foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
{
if($project.Object.AnalyzerReferences)
{
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
}
}
}
}
# $project.Type gives the language name like (C# or VB.NET)
$languageFolder = ""
if($project.Type -eq "C#")
{
$languageFolder = "cs"
}
if($project.Type -eq "VB.NET")
{
$languageFolder = "vb"
}
if($languageFolder -eq "")
{
return
}
foreach($analyzersPath in $analyzersPaths)
{
# Install language specific analyzers.
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
if (Test-Path $languageAnalyzersPath)
{
foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
{
if($project.Object.AnalyzerReferences)
{
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
}
}
}
}

View File

@@ -0,0 +1,56 @@
param($installPath, $toolsPath, $package, $project)
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve
foreach($analyzersPath in $analyzersPaths)
{
# Uninstall the language agnostic analyzers.
if (Test-Path $analyzersPath)
{
foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
{
if($project.Object.AnalyzerReferences)
{
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
}
}
}
}
# $project.Type gives the language name like (C# or VB.NET)
$languageFolder = ""
if($project.Type -eq "C#")
{
$languageFolder = "cs"
}
if($project.Type -eq "VB.NET")
{
$languageFolder = "vb"
}
if($languageFolder -eq "")
{
return
}
foreach($analyzersPath in $analyzersPaths)
{
# Uninstall language specific analyzers.
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
if (Test-Path $languageAnalyzersPath)
{
foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
{
if($project.Object.AnalyzerReferences)
{
try
{
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
}
catch
{
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
<div align="center"><h1 align="center">ThingsBlazor</a></h1></div>
<div align="center"><h1 align="center">ThingsBlazor</h1></div>
<div align="center"><h3 align="center">权限管理框架</h3></div>

View File

@@ -38,9 +38,9 @@ public sealed class OperDescAttribute : MoAttribute
static OperDescAttribute()
{
// 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中
Task.Factory.StartNew(ProcessQueue, TaskCreationOptions.LongRunning);
AppService = App.RootServices.GetService<IAppService>();
// 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中
Task.Factory.StartNew(ProcessQueueAsync, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
}
public OperDescAttribute(string description, bool isRecordPar = true, object localizerType = null)
@@ -64,32 +64,37 @@ public sealed class OperDescAttribute : MoAttribute
public override void OnException(MethodContext context)
{
//插入异常日志
SysOperateLog log = GetOperLog(LocalizerType, context);
if (App.HttpContext?.Request.Path.StartsWithSegments("/_blazor") == true)
{
//插入异常日志
SysOperateLog log = GetOperLog(LocalizerType, context);
log.Category = LogCateGoryEnum.Exception;//操作类型为异常
log.ExeStatus = false;//操作状态为失败
if (context.Exception is AppFriendlyException exception)
log.ExeMessage = exception?.Message;
else
log.ExeMessage = context.Exception?.ToString();
log.Category = LogCateGoryEnum.Exception;//操作类型为异常
log.ExeStatus = false;//操作状态为失败
if (context.Exception is AppFriendlyException exception)
log.ExeMessage = exception?.Message;
else
log.ExeMessage = context.Exception?.ToString();
OperDescAttribute.WriteToQueue(log);
OperDescAttribute.WriteToQueue(log);
}
}
public override void OnSuccess(MethodContext context)
{
//插入操作日志
SysOperateLog log = GetOperLog(LocalizerType, context);
OperDescAttribute.WriteToQueue(log);
if (App.HttpContext?.Request.Path.StartsWithSegments("/_blazor") == true)
{
//插入操作日志
SysOperateLog log = GetOperLog(LocalizerType, context);
OperDescAttribute.WriteToQueue(log);
}
}
private static SqlSugarClient _db = DbContext.GetDB<SysOperateLog>();
/// <summary>
/// 将日志消息写入数据库中
/// </summary>
private static async Task ProcessQueue()
private static async Task ProcessQueueAsync()
{
var db = DbContext.Db.GetConnectionScopeWithAttr<SysOperateLog>().CopyNew();
var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!;
while (!appLifetime.ApplicationStopping.IsCancellationRequested)
{
@@ -98,7 +103,7 @@ public sealed class OperDescAttribute : MoAttribute
var data = _logMessageQueue.ToListWithDequeue(); // 从日志队列中获取数据
if (data.Count > 0)
{
await db.InsertableWithAttr(data).ExecuteCommandAsync(appLifetime.ApplicationStopping).ConfigureAwait(false);//入库
await _db.InsertableWithAttr(data).ExecuteCommandAsync(appLifetime.ApplicationStopping).ConfigureAwait(false);//入库
}
}
catch (Exception ex)
@@ -115,7 +120,7 @@ public sealed class OperDescAttribute : MoAttribute
private SysOperateLog GetOperLog(Type? localizerType, MethodContext context)
{
var methodBase = context.Method;
var clientInfo = AppService.ClientInfo;
var userAgent = AppService.UserAgent;
string? paramJson = null;
if (IsRecordPar)
{
@@ -127,10 +132,10 @@ public sealed class OperDescAttribute : MoAttribute
{
parametersDict[parametersInfo[i].Name!] = args[i];
}
paramJson = parametersDict.ToJsonNetString();
paramJson = parametersDict.ToSystemTextJsonString();
}
var result = context.ReturnValue;
var resultJson = IsRecordPar ? result?.ToJsonNetString() : null;
var resultJson = IsRecordPar ? result?.ToSystemTextJsonString() : null;
//操作日志表实体
var log = new SysOperateLog
{
@@ -138,8 +143,8 @@ public sealed class OperDescAttribute : MoAttribute
Category = LogCateGoryEnum.Operate,
ExeStatus = true,
OpIp = AppService?.RemoteIpAddress ?? string.Empty,
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
OpBrowser = userAgent?.Browser,
OpOs = userAgent?.Platform,
OpTime = DateTime.Now,
OpAccount = UserManager.UserAccount,
ReqUrl = null,

View File

@@ -13,7 +13,7 @@ namespace ThingsGateway.Admin.Application;
/// <summary>
/// 需要角色授权权限
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class RolePermissionAttribute : Attribute
{
}
@@ -21,6 +21,7 @@ public sealed class RolePermissionAttribute : Attribute
/// <summary>
/// 忽略角色授权权限
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public sealed class IgnoreRolePermissionAttribute : Attribute
{
}

View File

@@ -0,0 +1,20 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
[AttributeUsage(AttributeTargets.Method)]
public sealed class LoginLogAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class LogoutLogAttribute : Attribute
{
}

View File

@@ -11,7 +11,7 @@
namespace ThingsGateway.Admin.Application;
[ThingsGateway.DependencyInjection.SuppressSniffer]
public class CacheConst
public static class CacheConst
{
/// <summary>
/// Token表缓存Key
@@ -33,22 +33,22 @@ public class CacheConst
/// <summary>
/// 资源表缓存Key
/// </summary>
public const string Cache_SysResource = $"{CacheConst.Cache_Prefix_Admin}SysResource:";
public const string Cache_SysResource = $"{CacheConst.Cache_Prefix_Admin}SysResource:List";
/// <summary>
/// 角色表缓存Key
/// </summary>
public const string Cache_SysRole = $"{CacheConst.Cache_Prefix_Admin}SysRole:";
public const string Cache_SysRole = $"{CacheConst.Cache_Prefix_Admin}SysRole:List";
/// <summary>
/// 用户表缓存Key
/// </summary>
public const string Cache_SysUser = $"{CacheConst.Cache_Prefix_Admin}SysUser:";
public const string Cache_SysUser = $"{CacheConst.Cache_Prefix_Admin}SysUser:Hash";
/// <summary>
/// 用户账号关系缓存Key
/// </summary>
public const string Cache_SysUserAccount = $"{CacheConst.Cache_Prefix_Admin}SysUserAccount:";
public const string Cache_SysUserAccount = $"{CacheConst.Cache_Prefix_Admin}SysUserAccount:Hash";
/// <summary>
/// 职位表缓存Key
@@ -58,7 +58,7 @@ public class CacheConst
/// <summary>
/// 机构表缓存Key
/// </summary>
public const string Cache_SysOrg = $"{CacheConst.Cache_Prefix_Admin}SysOrg:";
public const string Cache_SysOrg = $"{CacheConst.Cache_Prefix_Admin}SysOrg:List";
/// <summary>
/// 公司表缓存Key
@@ -67,12 +67,12 @@ public class CacheConst
/// <summary>
/// 公司表缓存Key
/// </summary>
public const string Cache_SysOrgTenant = $"{CacheConst.Cache_Prefix_Admin}OrgTenant:";
public const string Cache_SysOrgTenant = $"{CacheConst.Cache_Prefix_Admin}OrgTenant:Hash";
/// <summary>
/// Token表缓存Key
/// </summary>
public const string Cache_Token = $"{CacheConst.Cache_Prefix_Admin}Token:";
public const string Cache_Token = $"{CacheConst.Cache_Prefix_Admin}Token:Hash";
#region

View File

@@ -13,7 +13,7 @@ namespace ThingsGateway.Admin.Application;
/// <summary>
/// 通讯器常量
/// </summary>
public class HubConst
public static class HubConst
{
/// <summary>
/// 系统HubUrl

View File

@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Application;
/// 资源表常量
/// </summary>
[ThingsGateway.DependencyInjection.SuppressSniffer]
public class ResourceConst
public static class ResourceConst
{
/// <summary>
/// 系统内置编码

View File

@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Application;
/// 角色常量
/// </summary>
[ThingsGateway.DependencyInjection.SuppressSniffer]
public class RoleConst
public static class RoleConst
{
/// <summary>
/// api角色

View File

@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Application;
/// SqlSugar系统常量
/// </summary>
[ThingsGateway.DependencyInjection.SuppressSniffer]
public class SqlSugarConst
public static class SqlSugarConst
{
/// <summary>
/// DB_Admin

View File

@@ -8,6 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -15,7 +16,7 @@ namespace ThingsGateway.Admin.Application;
[ApiDescriptionSettings(false)]
[Route("api/auth")]
[LoggingMonitor]
[RequestAudit]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;
@@ -27,14 +28,28 @@ public class AuthController : ControllerBase
[HttpPost("login")]
[AllowAnonymous]
[LoginLog]
public Task<LoginOutput> LoginAsync([FromBody] LoginInput input)
{
return _authService.LoginAsync(input);
}
[HttpGet("oauth-login")]
[AllowAnonymous]
[SuppressRequestAudit]
public IActionResult OAuthLogin(string scheme = "Gitee", string returnUrl = "/")
{
var props = new AuthenticationProperties
{
RedirectUri = returnUrl
};
return Challenge(props, scheme);
}
[HttpPost("logout")]
[Authorize]
[IgnoreRolePermission]
[LogoutLog]
public Task LogoutAsync()
{
return _authService.LoginOutAsync();

View File

@@ -34,17 +34,20 @@ public class FileController : ControllerBase
return BadRequest("Invalid file name.");
}
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", fileName);
if (!System.IO.File.Exists(filePath))
var root = Directory.GetCurrentDirectory();
var wwwroot = Path.Combine(root, "wwwroot");
var filePath = Path.Combine(wwwroot, fileName);
#pragma warning disable CA3003
if ((!(fileName.StartsWith(@"../Logs") || fileName.StartsWith(@"..\Logs")) && filePath.Contains("..")) || !System.IO.File.Exists(filePath))
{
return NotFound();
}
#pragma warning restore CA3003
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
Response.Headers.Append("Access-Control-Expose-Headers", "Content-Disposition");
return File(fileStream, "application/octet-stream", (fileName.Replace('/', '_')));
return File(fileStream, "application/octet-stream", (Path.GetFileName(filePath).Replace('/', '_')));
}
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -17,7 +15,6 @@ using System.ComponentModel;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 登录
/// </summary>
@@ -25,7 +22,7 @@ namespace ThingsGateway.Admin.Application;
[Description("登录")]
[Route("openapi/auth")]
[Authorize(AuthenticationSchemes = "Bearer")]
[LoggingMonitor]
[RequestAudit]
[ApiController]
public class OpenApiController : ControllerBase
{
@@ -41,9 +38,9 @@ public class OpenApiController : ControllerBase
[AllowAnonymous]
public async Task<OpenApiLoginOutput> LoginAsync([FromBody] OpenApiLoginInput input)
{
var output = await _authService.LoginAsync(input.Adapt<LoginInput>(), false).ConfigureAwait(false);
var output = await _authService.LoginAsync(input.AdaptLoginInput(), false).ConfigureAwait(false);
var openApiLoginOutput = output.Adapt<OpenApiLoginOutput>();
var openApiLoginOutput = output.AdaptOpenApiLoginOutput();
return openApiLoginOutput;
}

View File

@@ -8,10 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;

View File

@@ -8,10 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
namespace ThingsGateway.Admin.Application;
/// <summary>

View File

@@ -8,10 +8,6 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
@@ -70,12 +66,9 @@ public class SysOrg : BaseEntity
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public OrgEnum Category { get; set; }
[SugarColumn(ColumnName = "Status", ColumnDescription = "启用")]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public bool Status { get; set; } = true;
}

View File

@@ -8,10 +8,6 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
@@ -46,7 +42,6 @@ public class SysPosition : BaseEntity
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public string Code { get; set; }
[SugarColumn(ColumnName = "Status", ColumnDescription = "启用")]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public bool Status { get; set; } = true;
@@ -57,5 +52,4 @@ public class SysPosition : BaseEntity
[SugarColumn(ColumnName = "Category", ColumnDescription = "分类")]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual PositionCategoryEnum Category { get; set; }
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using SqlSugar;
namespace ThingsGateway.Admin.Application;
/// <summary>

View File

@@ -8,14 +8,10 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components.Routing;
using Newtonsoft.Json;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;

View File

@@ -8,10 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
@@ -75,8 +71,6 @@ public class SysRole : BaseEntity
}
}
/// <summary>
/// 默认数据范围
/// </summary>

View File

@@ -8,14 +8,9 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Mapster;
using SqlSugar;
using Riok.Mapperly.Abstractions;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace ThingsGateway.Admin.Application;
@@ -31,7 +26,7 @@ public class SysUser : BaseEntity
///</summary>
[SugarColumn(ColumnDescription = "头像", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = false, Filterable = false)]
[AdaptIgnore]
[MapperIgnore]
public virtual string? Avatar { get; set; }
/// <summary>
@@ -175,14 +170,14 @@ public class SysUser : BaseEntity
/// </summary>
[SugarColumn(IsIgnore = true, IsJson = true)]
[AutoGenerateColumn(Ignore = true)]
public List<long> OrgAndPosIdList { get; set; } = new List<long>();
public List<long>? OrgAndPosIdList { get; set; } = new List<long>();
/// <summary>
/// 主管信息
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public UserSelectorOutput DirectorInfo { get; set; }
public UserSelectorOutput? DirectorInfo { get; set; }
#endregion
@@ -193,35 +188,35 @@ public class SysUser : BaseEntity
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public Dictionary<string, List<string>> ButtonCodeList { get; set; } = new();
public Dictionary<string, List<string>>? ButtonCodeList { get; set; } = new();
/// <summary>
/// 权限码集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public HashSet<string> PermissionCodeList { get; set; } = new();
public HashSet<string>? PermissionCodeList { get; set; } = new();
/// <summary>
/// 角色ID集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public HashSet<long> RoleIdList { get; set; } = new();
public HashSet<long>? RoleIdList { get; set; } = new();
/// <summary>
/// 机构及以下机构ID集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public HashSet<long> ScopeOrgChildList { get; set; }
public HashSet<long>? ScopeOrgChildList { get; set; }
/// <summary>
/// 模块集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public List<SysResource> ModuleList { get; set; } = new();
public List<SysResource>? ModuleList { get; set; } = new();
/// <summary>
/// 租户Id
@@ -230,7 +225,6 @@ public class SysUser : BaseEntity
[AutoGenerateColumn(Ignore = true)]
public long? TenantId { get; set; }
/// <summary>
/// 全局用戶
/// </summary>

View File

@@ -8,18 +8,13 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
using ThingsGateway.List;
using ThingsGateway.Common.List;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 会话信息
/// </summary>
[SugarTable("verificatinfo", TableDescription = "验证缓存表")]
[Tenant(SqlSugarConst.DB_TokenCache)]
public class VerificatInfo : PrimaryIdEntity
@@ -43,12 +38,14 @@ public class VerificatInfo : PrimaryIdEntity
[AutoGenerateColumn(Filterable = true, Sortable = true)]
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
[IgnoreExcel]
[System.ComponentModel.DataAnnotations.Key]
public override long Id { get; set; }
/// <summary>
/// 登录IP
/// </summary>
[AutoGenerateColumn(Filterable = true, Sortable = true, Width = 200)]
[SugarColumn(IsNullable = true)]
public string LoginIp { get; set; }
/// <summary>
@@ -82,7 +79,6 @@ public class VerificatInfo : PrimaryIdEntity
/// 登录设备
/// </summary>
[AutoGenerateColumn(Filterable = true, Sortable = true, Width = 100)]
[SugarColumn(IsNullable = true)]
public string Device { get; set; }
}

View File

@@ -12,7 +12,6 @@ namespace ThingsGateway.Admin.Application;
public enum DataScopeEnum
{
/// <summary>
/// 仅自己
/// </summary>
@@ -37,5 +36,4 @@ public enum DataScopeEnum
/// 自定义
/// </summary>
SCOPE_ORG_DEFINE,
}

View File

@@ -0,0 +1,35 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Extension;
namespace ThingsGateway.Admin.Application;
/// <inheritdoc/>
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class SchemeHelper
{
public static string GetOrCreate()
{
var path = "Keys/SchemeKey.txt";
if (File.Exists(path))
{
var data = File.ReadAllText(path);
return data;
}
else
{
var data = DateTime.UtcNow.ToDefaultDateTimeFormat();
Directory.CreateDirectory("Keys");
File.WriteAllText(path, data);
return data;
}
}
}

View File

@@ -8,7 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
global using Microsoft.Extensions.DependencyInjection;
namespace System.Logging;
global using ThingsGateway.Admin.Application;
global using ThingsGateway.Foundation;
public class RequestAudit
{
}

View File

@@ -0,0 +1,9 @@

using ThingsGateway.DependencyInjection;
namespace System;
[SuppressSniffer, AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public sealed class RequestAuditAttribute : Attribute
{
}

View File

@@ -0,0 +1,99 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using System.Reflection;
namespace ThingsGateway.Admin.Application;
public class RequestAuditData
{
/// <summary>
/// 分类
/// </summary>
public string CateGory { get; set; }
/// <summary>
/// 客户端信息
/// </summary>
public UserAgent Client { get; set; }
/// <summary>
/// 请求方法POST/GET
/// </summary>
public string Method { get; set; }
/// <summary>
/// 操作名称
/// </summary>
public string Operation { get; set; }
/// <summary>
/// 请求地址
/// </summary>
public string Path { get; set; }
/// <summary>
/// 方法名称
/// </summary>
public string ActionName { get; set; }
/// <summary>
/// 认证信息
/// </summary>
public List<AuthorizationClaims> AuthorizationClaims { get; set; }
/// <summary>
/// 控制器名
/// </summary>
public string ControllerName { get; set; }
/// <summary>
/// 异常信息
/// </summary>
public LogException Exception { get; set; }
public long TimeOperationElapsedMilliseconds { get; set; }
/// <summary>
/// 服务端
/// </summary>
public string LocalIPv4 { get; set; }
/// <summary>
/// 日志时间
/// </summary>
public DateTimeOffset LogDateTime { get; set; }
/// <summary>
/// 参数列表
/// </summary>
public List<Parameters> Parameters { get; set; }
/// <summary>
/// 客户端IPV4地址
/// </summary>
public string RemoteIPv4 { get; set; }
/// <summary>
/// 请求地址
/// </summary>
public string RequestUrl { get; set; }
/// <summary>
/// 返回信息
/// </summary>
public object ReturnInformation { get; set; }
/// <summary>
/// 验证错误信息
/// </summary>
public Validation Validation { get; set; }
public MethodInfo MethodInfo { get; set; }
}

View File

@@ -0,0 +1,289 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Logging;
using ThingsGateway.FriendlyException;
using ThingsGateway.Logging;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.UnifyResult;
namespace ThingsGateway.Admin.Application;
public class RequestAuditFilter : IAsyncActionFilter, IOrderedFilter
{
private const int FilterOrder = -3000;
public int Order => FilterOrder;
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var timeOperation = Stopwatch.StartNew();
var resultContext = await next().ConfigureAwait(false);
// 计算接口执行时间
timeOperation.Stop();
var controllerActionDescriptor = (context.ActionDescriptor as ControllerActionDescriptor);
// 获取动作方法描述器
var actionMethod = controllerActionDescriptor?.MethodInfo;
// 处理 Blazor Server
if (actionMethod == null)
{
return;
}
// 排除 WebSocket 请求处理
if (context.HttpContext.IsWebSocketRequest())
{
return;
}
// 如果贴了 [SuppressMonitor] 特性则跳过
if (actionMethod.IsDefined(typeof(SuppressRequestAuditAttribute), true)
|| actionMethod.DeclaringType.IsDefined(typeof(SuppressRequestAuditAttribute), true))
{
return;
}
// 只有方法贴有特性才进行审计
if (
!actionMethod.DeclaringType.IsDefined(typeof(RequestAuditAttribute), true)
&&
!actionMethod.IsDefined(typeof(RequestAuditAttribute), true))
{
return;
}
var logData = new RequestAuditData();
logData.TimeOperationElapsedMilliseconds = timeOperation.ElapsedMilliseconds;
var resultHttpContext = (resultContext as Microsoft.AspNetCore.Mvc.Filters.FilterContext).HttpContext;
// 获取 HttpContext 和 HttpRequest 对象
var httpContext = context.HttpContext;
var httpRequest = httpContext.Request;
// 获取客户端 Ipv4 地址
var remoteIPv4 = httpContext.GetRemoteIpAddressToIPv4();
logData.RemoteIPv4 = remoteIPv4;
var requestUrl = Uri.UnescapeDataString(httpRequest.GetRequestUrlAddress());
logData.RequestUrl = requestUrl;
object returnValue = null;
Type finalReturnType;
var result = resultContext.Result as IActionResult;
// 解析返回值
if (UnifyContext.CheckVaildResult(result, out var data))
{
returnValue = data;
finalReturnType = data?.GetType();
}
// 处理文件类型
else if (result is FileResult fresult)
{
returnValue = new
{
FileName = fresult.FileDownloadName,
fresult.ContentType,
Length = fresult is FileContentResult cresult ? (object)cresult.FileContents.Length : null
};
finalReturnType = fresult?.GetType();
}
else finalReturnType = result?.GetType();
logData.ReturnInformation = returnValue;
//获取客户端信息
var client = App.GetService<IAppService>().UserAgent;
//操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性
var option = $"{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}";
var desc = App.CreateLocalizerByType(controllerActionDescriptor.ControllerTypeInfo.AsType())[actionMethod.Name];
//获取特性
logData.CateGory = desc.Value;//传操作名称
logData.Operation = desc.Value;//传操作名称
logData.Client = client;
logData.Path = httpContext.Request.Path.Value;//请求地址
logData.Method = httpContext.Request.Method;//请求方法
logData.MethodInfo = actionMethod;//请求方法
logData.ControllerName = controllerActionDescriptor.ControllerName;
logData.ActionName = controllerActionDescriptor.ActionName;
logData.AuthorizationClaims = new();
// 获取授权用户
var user = httpContext.User;
foreach (var claim in user.Claims)
{
logData.AuthorizationClaims.Add(new AuthorizationClaims
{
Type = claim.Type,
Value = claim.Value,
});
}
logData.LocalIPv4 = httpContext.GetLocalIpAddressToIPv4();
logData.LogDateTime = DateTimeOffset.Now;
var parameterValues = context.ActionArguments;
logData.Parameters = new();
var parameters = actionMethod.GetParameters();
foreach (var parameter in parameters)
{
// 判断是否禁用记录特定参数
if (parameter.IsDefined(typeof(SuppressRequestAuditAttribute), false)) continue;
// 排除标记 [FromServices] 的解析
if (parameter.IsDefined(typeof(FromServicesAttribute), false)) continue;
var name = parameter.Name;
var parameterType = parameter.ParameterType;
_ = parameterValues.TryGetValue(name, out var value);
var par = new Parameters()
{
Name = name,
};
logData.Parameters.Add(par);
object rawValue = default;
// 文件类型参数
if (value is IFormFile || value is List<IFormFile>)
{
// 单文件
if (value is IFormFile formFile)
{
var fileSize = Math.Round(formFile.Length / 1024D);
rawValue = new
{
name = formFile.Name,
fileName = formFile.FileName,
length = formFile.Length,
contentType = formFile.ContentType
};
}
// 多文件
else if (value is List<IFormFile> formFiles)
{
var rawValues1 = new List<object>();
for (var i = 0; i < formFiles.Count; i++)
{
var file = formFiles[i];
var size = Math.Round(file.Length / 1024D);
var rawValue1 = new
{
name = file.Name,
fileName = file.FileName,
length = file.Length,
contentType = file.ContentType
};
rawValues1.Add(rawValue1);
}
rawValue = rawValues1;
}
}
// 处理 byte[] 参数类型
else if (value is byte[] byteArray)
{
rawValue = new
{
length = byteArray.Length,
};
}
// 处理基元类型,字符串类型和空值
else if (parameterType.IsPrimitive || value is string || value == null)
{
rawValue = value;
}
// 其他类型统一进行序列化
else
{
rawValue = value;
}
par.Value = rawValue;
}
// 获取异常对象情况
Exception exception = resultContext.Exception;
if (exception is AppFriendlyException friendlyException)
{
logData.Validation = new();
logData.Validation.Message = friendlyException.Message;
}
else if (exception != null)
{
logData.Exception = new();
logData.Exception.Message = exception.Message;
logData.Exception.StackTrace = exception.StackTrace;
logData.Exception.Type = HandleGenericType(exception.GetType());
}
// 创建日志记录器
var logger = httpContext.RequestServices.GetRequiredService<ILogger<RequestAudit>>();
var logContext = new LogContext();
logContext.Set(nameof(RequestAuditData), logData);
// 设置日志上下文
using var scope = logger.ScopeContext(logContext);
if (exception == null)
{
if (logger.IsEnabled(LogLevel.Information))
logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}");
}
else
{
if (logger.IsEnabled(LogLevel.Warning))
logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}");
}
}
/// <summary>
/// 处理泛型类型转字符串打印问题
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private static string HandleGenericType(Type type)
{
if (type == null) return string.Empty;
var typeName = type.FullName ?? (!string.IsNullOrEmpty(type.Namespace) ? type.Namespace + "." : string.Empty) + type.Name;
// 处理泛型类型问题
if (type.IsConstructedGenericType)
{
var prefix = type.GetGenericArguments()
.Select(genericArg => HandleGenericType(genericArg))
.Aggregate((previous, current) => previous + ", " + current);
typeName = typeName.Split('`').First() + "<" + prefix + ">";
}
return typeName;
}
}

View File

@@ -0,0 +1,9 @@

using ThingsGateway.DependencyInjection;
namespace System;
[SuppressSniffer, AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public sealed class SuppressRequestAuditAttribute : Attribute
{
}

View File

@@ -8,5 +8,15 @@
// QQ群605534569
//------------------------------------------------------------------------------
global using ThingsGateway;
global using ThingsGateway.NewLife.Extension;
global using BootstrapBlazor.Components;
global using Microsoft.Extensions.Localization;
global using Microsoft.Extensions.Options;
global using System.Diagnostics.CodeAnalysis;
global using System.Globalization;
global using ThingsGateway.Common;
global using ThingsGateway.DB;
global using ThingsGateway.NewLife.Extension;
global using ThingsGateway.SqlSugar;

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using ThingsGateway.NewLife;
namespace ThingsGateway.Admin.Application;
@@ -51,7 +49,7 @@ public class HardwareInfo
/// 进程占用内存
/// </summary>
[AutoGenerateColumn(Ignore = true)]
public string WorkingSet { get; set; }
public int WorkingSet { get; set; }
/// <summary>
/// 更新时间

View File

@@ -9,14 +9,13 @@
// ------------------------------------------------------------------------------
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Runtime.InteropServices;
using ThingsGateway.Extension;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Caching;
using ThingsGateway.NewLife.Threading;
using ThingsGateway.Schedule;
@@ -51,15 +50,25 @@ public class HardwareJob : IJob, IHardwareJob
#endregion
private ICache MemoryCache => App.CacheService;
private const string CacheKey = $"{CacheConst.Cache_HardwareInfo}HistoryHardwareInfo";
/// <inheritdoc/>
public async Task<List<HistoryHardwareInfo>> GetHistoryHardwareInfos()
{
using var db = DbContext.Db.GetConnectionScopeWithAttr<HistoryHardwareInfo>().CopyNew();
return await db.Queryable<HistoryHardwareInfo>().ToListAsync().ConfigureAwait(false);
var historyHardwareInfos = MemoryCache.Get<List<HistoryHardwareInfo>>(CacheKey);
if (historyHardwareInfos == null)
{
using var db = DbContext.GetDB<HistoryHardwareInfo>();
historyHardwareInfos = await db.Queryable<HistoryHardwareInfo>().Where(a => a.Date > DateTime.Now.AddDays(-3)).Take(1000).ToListAsync().ConfigureAwait(false);
MemoryCache.Set(CacheKey, historyHardwareInfos);
}
return historyHardwareInfos;
}
private bool error = false;
private DateTime hisInsertTime = default;
private SqlSugarClient _db = DbContext.GetDB<HistoryHardwareInfo>();
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
@@ -69,8 +78,7 @@ public class HardwareJob : IJob, IHardwareJob
{
if (HardwareInfo.MachineInfo == null)
{
await MachineInfo.RegisterAsync().ConfigureAwait(false);
HardwareInfo.MachineInfo = MachineInfo.Current;
HardwareInfo.MachineInfo = MachineInfo.GetCurrent();
string currentPath = Directory.GetCurrentDirectory();
DriveInfo drive = new(Path.GetPathRoot(currentPath));
@@ -84,7 +92,6 @@ public class HardwareJob : IJob, IHardwareJob
HardwareInfo.UUID = HardwareInfo.MachineInfo.UUID;
HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
}
}
catch
@@ -94,13 +101,13 @@ public class HardwareJob : IJob, IHardwareJob
{
HardwareInfo.MachineInfo.Refresh();
HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
HardwareInfo.WorkingSet = (Environment.WorkingSet / 1024.0 / 1024.0).ToString("F2");
HardwareInfo.WorkingSet = (Environment.WorkingSet / 1024.0 / 1024.0).ToInt();
error = false;
}
catch (Exception ex)
{
if (!error)
_logger.LogWarning(ex, _localizer["GetHardwareInfoFail"]);
_logger.LogWarning(ex, "Get Hardwareinfo Fail");
error = true;
}
@@ -111,22 +118,26 @@ public class HardwareJob : IJob, IHardwareJob
if (DateTime.Now > hisInsertTime.Add(TimeSpan.FromMilliseconds(HardwareInfoOptions.HistoryInterval)))
{
hisInsertTime = DateTime.Now;
using var db = DbContext.Db.GetConnectionScopeWithAttr<HistoryHardwareInfo>().CopyNew();
{
var his = new HistoryHardwareInfo()
{
Date = TimerX.Now,
DriveUsage = (100 - (HardwareInfo.DriveInfo.TotalFreeSpace * 100.00 / HardwareInfo.DriveInfo.TotalSize)).ToString("F2"),
Battery = (HardwareInfo.MachineInfo.Battery * 100).ToString("F2"),
DriveUsage = (100 - (HardwareInfo.DriveInfo.TotalFreeSpace * 100.00 / HardwareInfo.DriveInfo.TotalSize)).ToInt(),
Battery = (HardwareInfo.MachineInfo.Battery * 100).ToInt(),
MemoryUsage = (HardwareInfo.WorkingSet),
CpuUsage = (HardwareInfo.MachineInfo.CpuRate * 100).ToString("F2"),
Temperature = (HardwareInfo.MachineInfo.Temperature).ToString("F2"),
CpuUsage = (HardwareInfo.MachineInfo.CpuRate * 100).ToInt(),
Temperature = (HardwareInfo.MachineInfo.Temperature).ToInt(),
};
await db.Insertable(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
await _db.InsertableT(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
MemoryCache.Remove(CacheKey);
}
var sevenDaysAgo = TimerX.Now.AddDays(-HardwareInfoOptions.DaysAgo);
//删除特定信息
await db.Deleteable<HistoryHardwareInfo>(a => a.Date <= sevenDaysAgo).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
var result = await _db.Deleteable<HistoryHardwareInfo>(a => a.Date <= sevenDaysAgo).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
if (result > 0)
{
MemoryCache.Remove(CacheKey);
}
}
}
error = false;
@@ -137,10 +148,9 @@ public class HardwareJob : IJob, IHardwareJob
catch (Exception ex)
{
if (!error)
_logger.LogWarning(ex, _localizer["GetHardwareInfoFail"]);
_logger.LogWarning(ex, "Get Hardwareinfo Fail");
error = true;
}
}
}
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using SqlSugar;
namespace ThingsGateway.Admin.Application;
/// <inheritdoc/>
@@ -19,23 +17,23 @@ public class HistoryHardwareInfo
{
/// <inheritdoc/>
[SugarColumn(ColumnDescription = "磁盘使用率")]
public string DriveUsage { get; set; }
public int DriveUsage { get; set; }
/// <inheritdoc/>
[SugarColumn(ColumnDescription = "内存")]
public string MemoryUsage { get; set; }
public int MemoryUsage { get; set; }
/// <inheritdoc/>
[SugarColumn(ColumnDescription = "CPU使用率")]
public string CpuUsage { get; set; }
public int CpuUsage { get; set; }
/// <inheritdoc/>
[SugarColumn(ColumnDescription = "温度")]
public string Temperature { get; set; }
public int Temperature { get; set; }
/// <inheritdoc/>
[SugarColumn(ColumnDescription = "电池")]
public string Battery { get; set; }
public int Battery { get; set; }
/// <inheritdoc/>
[SugarColumn(ColumnDescription = "时间")]

View File

@@ -17,7 +17,6 @@ namespace ThingsGateway.Admin.Application;
/// </summary>
public class JobPersistence : IJobPersistence
{
/// <summary>
/// 作业调度服务启动时
/// </summary>
@@ -61,7 +60,6 @@ public class JobPersistence : IJobPersistence
public async Task OnTriggerChangedAsync(PersistenceTriggerContext context)
{
await Task.CompletedTask.ConfigureAwait(false);
}
/// <summary>

View File

@@ -27,7 +27,7 @@ public class LogJob : IJob
private static async Task DeleteSysOperateLog(int daysAgo, CancellationToken stoppingToken)
{
using var db = DbContext.Db.GetConnectionScopeWithAttr<SysOperateLog>().CopyNew();
using var db = DbContext.GetDB<SysOperateLog>();
var time = DateTime.Now.AddDays(-daysAgo);
await db.DeleteableWithAttr<SysOperateLog>().Where(u => u.OpTime < time).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false); // 删除操作日志
}

View File

@@ -1,459 +1,393 @@
{
"ThingsGateway.Admin.Application.BaseDataEntity": {
"CreateOrgId": "CreateOrgId"
"ThingsGateway.Admin.Application.AppConfig": {
"LoginPolicy": "LoginPolicy",
"PagePolicy": "PagePolicy",
"PasswordPolicy": "PasswordPolicy",
"WebsitePolicy": "WebsitePolicy"
},
"ThingsGateway.Admin.Application.BaseEntity": {
"SortCode": "SortCode",
"CreateTime": "CreateTime",
"CreateUser": "CreateUser",
"UpdateTime": "UpdateTime",
"UpdateUser": "UpdateUser"
"ThingsGateway.Admin.Application.AuthController": {
"AuthController": "Login API",
"LoginAsync": "Login",
"LogoutAsync": "Logout"
},
"ThingsGateway.Admin.Application.AuthService": {
"AuthErrorMax": "Account password error, will be locked for {1} minutes after exceeding {0} times, error count {2}",
"MustDesc": "Password needs to be encrypted with DESC before passing",
"OrgDisable": "The affiliated company/department has been deactivated, please contact the administrator",
"PasswordError": "Too many password errors, please try again in {0} minutes",
"SingleLoginWarn": "Your account is logged in elsewhere",
"TenantNull": "The tenant does not exist",
"UserDisable": "Account {0} has been disabled",
"UserNoModule": "This account has not been assigned a module. Please contact the administrator",
"UserNull": "User {0} does not exist"
},
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
"UserExpire": "User expired, please login again"
},
"ThingsGateway.Admin.Application.SysUser": {
"Disable": "Disable",
"Enable": "Enable",
"GrantRole": "GrantRole",
"ExitVerificat": "You have been forcibly logged out",
"PasswordEdited": "Password changed, logged out",
"Avatar": "Avatar",
"Account": "Account",
"Account.Required": "Account.Required",
"Password": "Password",
"Status": "Status",
"Phone": "Phone",
"Email": "Email",
"LastLoginIp": "LastLoginIp",
"LastLoginDevice": "LastLoginDevice",
"LastLoginTime": "LastLoginTime",
"LastLoginAddress": "LastLoginAddress",
"LatestLoginIp": "LatestLoginIp",
"LatestLoginTime": "LatestLoginTime",
"LatestLoginDevice": "LatestLoginDevice",
"LatestLoginAddress": "LatestLoginAddress",
"OrgNames": "OrgNames",
"PositionName": "PositionName",
"OrgId": "Org",
"PositionId": "Position",
"DirectorId": "Director",
"CheckSelf": "Prohibit {0} yourself",
"CanotDeleteAdminUser": "Cannot delete built-in super admin user",
"CanotEditAdminUser": "Cannot edit super admin user",
"CanotGrantAdmin": "Cannot assign admins roles",
"EmailDup": "Duplicate email {0} exists",
"AccountDup": "Duplicate account {0} exists",
"CanotDeleteSelf": "Cannot delete yourself",
"EmailError": "Email format error {0}",
"PhoneError": "Phone number format error {0}",
"NoOrg": "The organization does not exist",
"DirectorSelf": "Cannot set oneself as the supervisor",
"DemoCanotUpdatePassword": "DEMO environment does not allow password modification",
"OldPasswordError": "Incorrect old password",
"ConfirmPasswordDiff": "Passwords entered twice are inconsistent",
"PasswordLengthLess": "Password length cannot be less than {0}",
"PasswordMustNum ": "Password must contain numbers",
"PasswordMustLow": "Password must contain lowercase letters",
"PasswordMustUpp": "Password must contain uppercase letters",
"PasswordMustSpecial": "Password must contain special characters"
},
"ThingsGateway.Admin.Application.SysRole": {
"Code": "Code",
"Name": "Name",
"Name.Required": "{0} is required",
"Category": "Category",
"OrgId": "Org",
"Global": "Global",
"Status": "Status",
"CanotDeleteAdmin": "Cannot delete built-in super admin role",
"CanotEditAdmin": "Cannot edit super admin role",
"CanotGrantAdmin": "Cannot assign admins roles",
"NameDup": "Duplicate role name {0}",
"OrgNotNull": "Organization cannot be null",
"SameOrgNameDup": "Duplicate role name exists: {0}",
"CannotRoleScopeAll": "Organization role cannot select global data scope",
"CodeDup": "Duplicate code exists: {0}"
},
"ThingsGateway.Admin.Application.RoleCategoryEnum": {
"Global": "Global",
"Org": "Org"
},
"ThingsGateway.Admin.Application.DataScopeEnum": {
"SCOPE_SELF": "Self",
"SCOPE_ALL": "All",
"SCOPE_ORG": "OnlyOrg",
"SCOPE_ORG_CHILD": "OrgChild",
"SCOPE_ORG_DEFINE": "Define"
"SCOPE_ORG_DEFINE": "Define",
"SCOPE_SELF": "Self"
},
"ThingsGateway.Admin.Application.DefaultDataScope": {
"ScopeCategory": "DataScope",
"ScopeDefineOrgIdList": "DefineOrgList"
},
"ThingsGateway.Admin.Application.SysResource": {
"Title": "Title",
"Module": "Module",
"Title.Required": "{0} is required",
"Href.Required": "{0} is required",
"Icon": "Icon",
"Href": "Path",
"Code": "Code",
"Category": "Category",
"Target": "Target",
"NavLinkMatch": "NavLinkMatch",
"ParentId": "Parent",
"ResourceDup": "Duplicate name {0} exists",
"ResourceParentChoiceSelf": "Parent cannot choose itself",
"ResourceParentNull": "Parent does not exist {0}",
"NotFoundResource": "System exception, menu not found",
"ModuleIdDiff": "Module is inconsistent with parent menu",
"CanotDeleteSystemResource": "Cannot delete system resource {0}",
"ResourceMenuHrefNotNull": "Menu href cannot null"
"ThingsGateway.Admin.Application.DictTypeEnum": {
"Define": "Business",
"System": "System"
},
"ThingsGateway.Admin.Application.SysOrgCopyInput": {
"TargetId": "Target",
"ContainsChild": "ContainsChild",
"ContainsPosition": "ContainsPosition"
},
"ThingsGateway.Admin.Application.SysPosition": {
"Category.Required": "{0} is a required field",
"Name.Required": "{0} is a required field",
"Code.Required": "{0} is a required field",
"OrgId.MinValue": "{0} is a required field",
"Category": "Category",
"Name": "Name",
"Code": "Code",
"Status": "Status",
"OrgId": "Organization",
"Remark": "Remarks",
"Dup": "Duplicate position exists with Category {0} and Name {1}",
"CodeDup": "Duplicate code {0} exists",
"NameDup": "Duplicate name {0} exists",
"CanotContainsSelf": "Cannot contain itself",
"TargetNameDup": "Target node has duplicate name {0}",
"ParentChoiceSelf": "Parent cannot be itself",
"ParentNull": "Parent does not exist {0}",
"DeleteUserFirst": "Please remove the users under the position first"
},
"ThingsGateway.Admin.Application.SysOrg": {
"Category.Required": "{0} is a required field",
"Name.Required": "{0} is a required field",
"Code.Required": "{0} is a required field",
"Category": "Category",
"Name": "Name",
"Code": "Code",
"Status": "Status",
"ParentId": "ParentOrg",
"Names": "Names",
"Remark": "Remarks",
"DirectorId": "Director",
"Dup": "Duplicate organization exists with Category {0} and Name {1}",
"CodeDup": "Duplicate code {0} exists",
"NameDup": "Duplicate name {0} exists",
"CanotContainsSelf": "Cannot contain itself",
"TargetNameDup": "Target node has duplicate name {0}",
"ParentChoiceSelf": "Parent cannot be itself",
"ParentNull": "Parent does not exist {0}",
"DeleteUserFirst": "Please remove the users under the organization first",
"DeleteRoleFirst": "Please remove the roles under the organization first",
"DeletePositionFirst": "Please remove the positions under the organization first",
"RootOrg": "Unable to create top-level organization"
},
"ThingsGateway.Admin.Application.OrgEnum": {
"COMPANY": "Company",
"DEPT": "Dept"
},
"ThingsGateway.Admin.Application.PositionCategoryEnum": {
"HIGH": "High",
"MIDDLE": "Middle",
"LOW": "Low"
},
//controller
"ThingsGateway.Admin.Application.AuthController": {
//auth
"AuthController": "Login API",
"LoginAsync": "Login",
"LogoutAsync": "Logout"
},
"ThingsGateway.Admin.Application.TestController": {
//auth
"TestController": "Test API",
"Test": "Test"
},
"ThingsGateway.Admin.Application.OpenApiAuthController": {
//auth
"OpenApiAuthController": "Login API",
"LoginAsync": "Login",
"LogoutAsync": "Logout"
},
"ThingsGateway.Admin.Application.FileService": {
"FileNullError": "File cannot be empty",
"FileLengthError": "File size cannot exceed {0} M",
"FileNullError": "File cannot be empty",
"FileTypeError": "Not supported format {0}"
},
"ThingsGateway.Admin.Application.UnifyResultProvider": {
"TokenOver": "Login has expired, please login again",
"NoPermission": "Access denied, no permission"
},
"ThingsGateway.Admin.Application.AuthService": {
"TenantNull": "The tenant does not exist",
"OrgDisable": "The affiliated company/department has been deactivated, please contact the administrator",
"SingleLoginWarn": "Your account is logged in elsewhere",
"UserNull": "User {0} does not exist",
"MustDesc": "Password needs to be encrypted with DESC before passing",
"PasswordError": "Too many password errors, please try again in {0} minutes",
"UserDisable": "Account {0} has been disabled",
"UserNoModule": "This account has not been assigned a module. Please contact the administrator",
"AuthErrorMax": "Account password error, will be locked for {1} minutes after exceeding {0} times, error count {2}"
},
"ThingsGateway.Admin.Application.HardwareInfo": {
"Environment": "HostEnvironment",
"FrameworkDescription": ".NETFramework",
"OsArchitecture": "System Architecture",
"UUID": "UUID",
"UpdateTime": "UpdateTime"
"UpdateTime": "UpdateTime",
"UUID": "UUID"
},
"ThingsGateway.Admin.Application.HistoryHardwareInfo": {
"DriveUsage": "Disk Usage",
"MemoryUsage": "Memory",
"CpuUsage": "CPU Usage",
"Temperature": "Temperature",
"Battery": "Battery"
},
//oper
"ThingsGateway.Admin.Application.OperDescAttribute": {
//dict
"SaveDict": "Modify dictionary",
"DeleteDict": "Delete dictionary",
"EditLoginPolicy": "Modify login policy",
"EditPasswordPolicy": "Modify password policy",
"EditPagePolicy": "Modify page policy",
"EditWebsitePolicy": "Modify website settings",
//operlog
"DeleteOperLog": "Delete operation log",
"ExportOperLog": "Export operation log",
//resource
"SaveResource": "Modify resource",
"DeleteResource": "Delete resource",
//role
"SaveRole": "Modify role",
"DeleteRole": "Delete role",
"RoleGrantResource": "Role grant resource",
"RoleGrantUser": "Role grant user",
"RoleGrantApiPermission": "Role grant OpenApi",
"GrantApi": "GrantApi",
"GrantUser": "GrantUser",
"GrantRole": "GrantRole",
"GrantResource": "GrantResource",
//user
"SaveUser": "Modify user",
"DeleteuSER": "Delete user",
"ResetPassword": "Reset pw",
"UserGrantRole": "User grant role",
"UserGrantResource": "User grant resource",
"UserGrantApiPermission": "User grant OpenApi",
//usercenter
"UpdateUserInfo": "Update personal information",
"WorkbenchInfo": "Update personal workbench",
"UpdatePassword": "Update personal password",
//session
"ExitVerificat": "Force token off",
"ExitSession": "Force session off",
"CopyOrg": "Copy Organization",
"DeleteOrg": "Delete Organization",
"SaveOrg": "Save Organization",
"DeletePosition": "Delete Position",
"SavePosition": "Save Position",
"NoPermission": "No Permission",
"CopyResource": "CopyResource",
"ChangeParentResource": "ChangeParentResource"
},
//service
"ThingsGateway.Admin.Application.HardwareJob": {
"GetHardwareInfoFail": "Get Hardwareinfo Fail"
},
//dto
"ThingsGateway.Admin.Application.UserSelectorOutput": {
"ThingsGateway.Admin.Application.HistoryHardwareInfo": {
"Battery": "Battery",
"CpuUsage": "CPU Usage",
"DriveUsage": "Disk Usage",
"MemoryUsage": "Memory",
"Temperature": "Temperature"
},
"ThingsGateway.Admin.Application.LogCateGoryEnum": {
"Exception": "Exception",
"Login": "Login",
"Logout": "Logout",
"Operate": "Operation"
},
"ThingsGateway.Admin.Application.LogEnum": {
"FAIL": "Fail",
"SUCCESS": "Success"
},
"ThingsGateway.Admin.Application.LoginInput": {
"Account": "Account",
"OrgId": "Org"
"Account.Required": "{0} is required",
"Password": "Password",
"Password.Required": "{0} is required"
},
"ThingsGateway.Admin.Application.LoginPolicy": {
"ErrorCount": "Login error count lock threshold",
"ErrorCount.MinValue": "{0} value is too small",
"ErrorLockTime": "Login error lock duration (min)",
"ErrorLockTime.MinValue": "{0} value is too small",
"ErrorResetTime": "Login error count expiration duration (min)",
"ErrorResetTime.MinValue": "{0} value is too small",
"SingleOpen": "Single user login switch",
"VerificatExpireTime": "Login expiration time (min)",
"VerificatExpireTime.MinValue": "{0} value is too small"
},
"ThingsGateway.Admin.Application.LogoutInput": {
"VerificatId.Required": "{0} is required"
},
"ThingsGateway.Admin.Application.OpenApiAuthController": {
"LoginAsync": "Login",
"LogoutAsync": "Logout",
"OpenApiAuthController": "Login API"
},
"ThingsGateway.Admin.Application.OperateLogPageInput": {
"Account": "Account",
"Category": "Category",
"SearchDate": "SearchDate"
},
"ThingsGateway.Admin.Application.OperDescAttribute": {
"ChangeParentResource": "ChangeParentResource",
"CopyOrg": "Copy Organization",
"CopyResource": "CopyResource",
"DeleteDict": "Delete dictionary",
"DeleteOperLog": "Delete operation log",
"DeleteOrg": "Delete Organization",
"DeletePosition": "Delete Position",
"DeleteResource": "Delete resource",
"DeleteRole": "Delete role",
"DeleteuSER": "Delete user",
"EditLoginPolicy": "Modify login policy",
"EditPagePolicy": "Modify page policy",
"EditPasswordPolicy": "Modify password policy",
"EditWebsitePolicy": "Modify website settings",
"ExitSession": "Force session off",
"ExitVerificat": "Force token off",
"ExportOperLog": "Export operation log",
"GrantApi": "GrantApi",
"GrantResource": "GrantResource",
"GrantRole": "GrantRole",
"GrantUser": "GrantUser",
"NoPermission": "No Permission",
"ResetPassword": "Reset pw",
"RoleGrantApiPermission": "Role grant OpenApi",
"RoleGrantResource": "Role grant resource",
"RoleGrantUser": "Role grant user",
"SaveDict": "Modify dictionary",
"SaveOrg": "Save Organization",
"SavePosition": "Save Position",
"SaveResource": "Modify resource",
"SaveRole": "Modify role",
"SaveUser": "Modify user",
"UpdatePassword": "Update personal password",
"UpdateUserInfo": "Update personal information",
"UserGrantApiPermission": "User grant OpenApi",
"UserGrantResource": "User grant resource",
"UserGrantRole": "User grant role",
"WorkbenchInfo": "Update personal workbench"
},
"ThingsGateway.Admin.Application.OrgEnum": {
"COMPANY": "Company",
"DEPT": "Dept"
},
"ThingsGateway.Admin.Application.PagePolicy": {
"Razor": "Default homepage",
"Shortcuts": "Default shortcuts"
},
"ThingsGateway.Admin.Application.PasswordPolicy": {
"DefaultPassword": "Default user password",
"DefaultPassword.Required": "{0} is required",
"PasswordContainChar": "Contain special characters",
"PasswordContainLower": "Contain lowercase letters",
"PasswordContainNum": "Contain numbers",
"PasswordContainUpper": "Contain uppercase letters",
"PasswordMinLen": "Minimum password length",
"PasswordMinLen.MinValue": "{0} value is too small"
},
"ThingsGateway.Admin.Application.PositionCategoryEnum": {
"HIGH": "High",
"LOW": "Low",
"MIDDLE": "Middle"
},
"ThingsGateway.Admin.Application.ResourceCategoryEnum": {
"Button": "Button",
"Menu": "Menu",
"Module": "Module"
},
"ThingsGateway.Admin.Application.ResourceTableSearchModel": {
"Module": "Module",
"Href": "Path",
"Module": "Module",
"Title": "Title"
},
"ThingsGateway.Admin.Application.WorkbenchInfo": {
"Razor": "Homepage",
"Shortcuts": "Shortcuts"
"ThingsGateway.Admin.Application.RoleCategoryEnum": {
"Global": "Global",
"Org": "Org"
},
"ThingsGateway.Admin.Application.UpdatePasswordInput": {
"Password": "Password",
"NewPassword": "New password",
"ConfirmPassword": "Confirm password",
"Password.Required": "{0} is required",
"NewPassword.Required": "{0} is required",
"ConfirmPassword.Required": "{0} is required"
},
"ThingsGateway.Admin.Application.VerificatInfo": {
"Expire": "Expire(min)",
"Online": "Online",
"VerificatTimeout": "VerificatTimeout",
"Device": "Device",
"LoginIp": "LoginIp",
"LoginTime": "LoginTime"
},
"ThingsGateway.Admin.Application.SessionOutput": {
"Account": "Account",
"Online": "Online status",
"LatestLoginIp": "Latest login IP",
"LatestLoginTime": "Latest login time",
"Online": "Online status",
"VerificatCount": "Token count"
},
"ThingsGateway.Admin.Application.SysDict": {
"Category.Required": "{0} is required",
"Name.Required": "{0} is required",
"Code.Required": "{0} is required",
"Category": "Category",
"Name": "Name",
"Category.Required": "{0} is required",
"Code": "Code",
"Remark": "Remark",
"Code.Required": "{0} is required",
"DemoCanotUpdateWebsitePolicy": "DEMO environment does not allow modifying website settings",
"DictDup": "Duplicate configuration exists, category {0}, name {1}"
"DictDup": "Duplicate configuration exists, category {0}, name {1}",
"Name": "Name",
"Name.Required": "{0} is required",
"Remark": "Remark"
},
"ThingsGateway.Admin.Application.SysOperateLog": {
"Category": "Category",
"ClassName": "ClassName",
"ExeMessage": "ExeMessage",
"MethodName": "MethodName",
"ParamJson": "ParamJson",
"ReqMethod": "RequestMethod",
"ReqUrl": "RequestUrl",
"ResultJson": "ResultJson",
"Category": "Category",
"ExeStatus": "ExeStatus",
"MethodName": "MethodName",
"Name": "Name",
"OpAccount": "OpAccount",
"OpBrowser": "OpBrowser",
"OpIp": "OpIp",
"OpOs": "OpOs",
"OpTime": "OpTime",
"ParamJson": "ParamJson",
"ReqMethod": "RequestMethod",
"ReqUrl": "RequestUrl",
"ResultJson": "ResultJson",
"VerificatId": "VerificatId"
},
"ThingsGateway.Admin.Application.OperateLogPageInput": {
"SearchDate": "SearchDate",
"Account": "Account",
"Category": "Category"
"ThingsGateway.Admin.Application.SysOrg": {
"CanotContainsSelf": "Cannot contain itself",
"Category": "Category",
"Category.Required": "{0} is a required field",
"Code": "Code",
"Code.Required": "{0} is a required field",
"CodeDup": "Duplicate code {0} exists",
"DeletePositionFirst": "Please remove the positions under the organization first",
"DeleteRoleFirst": "Please remove the roles under the organization first",
"DeleteUserFirst": "Please remove the users under the organization first",
"DirectorId": "Director",
"Dup": "Duplicate organization exists with Category {0} and Name {1}",
"Name": "Name",
"Name.Required": "{0} is a required field",
"NameDup": "Duplicate name {0} exists",
"Names": "Names",
"ParentChoiceSelf": "Parent cannot be itself",
"ParentId": "ParentOrg",
"ParentNull": "Parent does not exist {0}",
"Remark": "Remarks",
"RootOrg": "Unable to create top-level organization",
"Status": "Status",
"TargetNameDup": "Target node has duplicate name {0}"
},
"ThingsGateway.Admin.Application.LoginInput": {
"Account": "Account",
"Password": "Password",
"Account.Required": "{0} is required",
"Password.Required": "{0} is required"
"ThingsGateway.Admin.Application.SysOrgCopyInput": {
"ContainsChild": "ContainsChild",
"ContainsPosition": "ContainsPosition",
"TargetId": "Target"
},
"ThingsGateway.Admin.Application.LogoutInput": {
"VerificatId.Required": "{0} is required"
"ThingsGateway.Admin.Application.SysPosition": {
"CanotContainsSelf": "Cannot contain itself",
"Category": "Category",
"Category.Required": "{0} is a required field",
"Code": "Code",
"Code.Required": "{0} is a required field",
"CodeDup": "Duplicate code {0} exists",
"DeleteUserFirst": "Please remove the users under the position first",
"Dup": "Duplicate position exists with Category {0} and Name {1}",
"Name": "Name",
"Name.Required": "{0} is a required field",
"NameDup": "Duplicate name {0} exists",
"OrgId": "Organization",
"OrgId.MinValue": "{0} is a required field",
"ParentChoiceSelf": "Parent cannot be itself",
"ParentNull": "Parent does not exist {0}",
"Remark": "Remarks",
"Status": "Status",
"TargetNameDup": "Target node has duplicate name {0}"
},
"ThingsGateway.Admin.Application.AppConfig": {
"LoginPolicy": "LoginPolicy",
"PasswordPolicy": "PasswordPolicy",
"PagePolicy": "PagePolicy",
"WebsitePolicy": "WebsitePolicy"
},
"ThingsGateway.Admin.Application.LoginPolicy": {
"SingleOpen": "Single user login switch",
"ErrorLockTime": "Login error lock duration (min)",
"ErrorResetTime": "Login error count expiration duration (min)",
"ErrorCount": "Login error count lock threshold",
"VerificatExpireTime": "Login expiration time (min)",
"ErrorLockTime.MinValue": "{0} value is too small",
"ErrorResetTime.MinValue": "{0} value is too small",
"ErrorCount.MinValue": "{0} value is too small",
"VerificatExpireTime.MinValue": "{0} value is too small"
},
"ThingsGateway.Admin.Application.PagePolicy": {
"Shortcuts": "Default shortcuts",
"Razor": "Default homepage"
},
"ThingsGateway.Admin.Application.PasswordPolicy": {
"DefaultPassword": "Default user password",
"DefaultPassword.Required": "{0} is required",
"PasswordMinLen": "Minimum password length",
"PasswordMinLen.MinValue": "{0} value is too small",
"PasswordContainNum": "Contain numbers",
"PasswordContainLower": "Contain lowercase letters",
"PasswordContainUpper": "Contain uppercase letters",
"PasswordContainChar": "Contain special characters"
},
"ThingsGateway.Admin.Application.WebsitePolicy": {
"WebStatus": "WebStatus",
"CloseTip": "CloseTip",
"CloseTip.Required": "{0} is required"
},
//enum
"ThingsGateway.Admin.Application.ResourceCategoryEnum": {
"ThingsGateway.Admin.Application.SysResource": {
"CanotDeleteSystemResource": "Cannot delete system resource {0}",
"Category": "Category",
"Code": "Code",
"Href": "Path",
"Href.Required": "{0} is required",
"Icon": "Icon",
"Module": "Module",
"Menu": "Menu",
"Button": "Button"
"ModuleIdDiff": "Module is inconsistent with parent menu",
"NavLinkMatch": "NavLinkMatch",
"NotFoundResource": "System exception, menu not found",
"ParentId": "Parent",
"ResourceDup": "Duplicate name {0} exists",
"ResourceMenuHrefNotNull": "Menu href cannot null",
"ResourceParentChoiceSelf": "Parent cannot choose itself",
"ResourceParentNull": "Parent does not exist {0}",
"Target": "Target",
"Title": "Title",
"Title.Required": "{0} is required"
},
"ThingsGateway.Admin.Application.SysRole": {
"CannotRoleScopeAll": "Organization role cannot select global data scope",
"CanotDeleteAdmin": "Cannot delete built-in super admin role",
"CanotEditAdmin": "Cannot edit super admin role",
"CanotGrantAdmin": "Cannot assign admins roles",
"Category": "Category",
"Code": "Code",
"CodeDup": "Duplicate code exists: {0}",
"Global": "Global",
"Name": "Name",
"Name.Required": "{0} is required",
"NameDup": "Duplicate role name {0}",
"OrgId": "Org",
"OrgNotNull": "Organization cannot be null",
"SameOrgNameDup": "Duplicate role name exists: {0}",
"Status": "Status"
},
"ThingsGateway.Admin.Application.SysUser": {
"Account": "Account",
"Account.Required": "Account.Required",
"AccountDup": "Duplicate account {0} exists",
"Avatar": "Avatar",
"CanotDeleteAdminUser": "Cannot delete built-in super admin user",
"CanotDeleteSelf": "Cannot delete yourself",
"CanotEditAdminUser": "Cannot edit super admin user",
"CanotGrantAdmin": "Cannot assign admins roles",
"CheckSelf": "Prohibit {0} yourself",
"ConfirmPasswordDiff": "Passwords entered twice are inconsistent",
"DemoCanotUpdatePassword": "DEMO environment does not allow password modification",
"DirectorId": "Director",
"DirectorSelf": "Cannot set oneself as the supervisor",
"Disable": "Disable",
"Email": "Email",
"EmailDup": "Duplicate email {0} exists",
"EmailError": "Email format error {0}",
"Enable": "Enable",
"ExitVerificat": "You have been forcibly logged out",
"GrantRole": "GrantRole",
"LastLoginAddress": "LastLoginAddress",
"LastLoginDevice": "LastLoginDevice",
"LastLoginIp": "LastLoginIp",
"LastLoginTime": "LastLoginTime",
"LatestLoginAddress": "LatestLoginAddress",
"LatestLoginDevice": "LatestLoginDevice",
"LatestLoginIp": "LatestLoginIp",
"LatestLoginTime": "LatestLoginTime",
"NoOrg": "The organization does not exist",
"OldPasswordError": "Incorrect old password",
"OrgId": "Org",
"OrgNames": "OrgNames",
"Password": "Password",
"PasswordEdited": "Password changed, logged out",
"PasswordLengthLess": "Password length cannot be less than {0}",
"PasswordMustLow": "Password must contain lowercase letters",
"PasswordMustNum": "Password must contain numbers",
"PasswordMustSpecial": "Password must contain special characters",
"PasswordMustUpp": "Password must contain uppercase letters",
"Phone": "Phone",
"PhoneError": "Phone number format error {0}",
"PositionId": "Position",
"PositionName": "PositionName",
"Status": "Status"
},
"ThingsGateway.Admin.Application.TargetEnum": {
"_self": "Current window",
"_blank": "New window",
"_parent": "Parent window",
"_self": "Current window",
"_top": "Top window"
},
"ThingsGateway.Admin.Application.DictTypeEnum": {
"System": "System",
"Define": "Business"
"ThingsGateway.Admin.Application.TestController": {
"Test": "Test",
"TestController": "Test API"
},
"ThingsGateway.Admin.Application.LogCateGoryEnum": {
"Login": "Login",
"Logout": "Logout",
"Operate": "Operation",
"Exception": "Exception"
"ThingsGateway.Admin.Application.UnifyResultProvider": {
"NoPermission": "Access denied, no permission",
"TokenOver": "Login has expired, please login again"
},
"ThingsGateway.Admin.Application.LogEnum": {
"SUCCESS": "Success",
"FAIL": "Fail"
"ThingsGateway.Admin.Application.UpdatePasswordInput": {
"ConfirmPassword": "Confirm password",
"ConfirmPassword.Required": "{0} is required",
"NewPassword": "New password",
"NewPassword.Required": "{0} is required",
"Password": "Password",
"Password.Required": "{0} is required"
},
"ThingsGateway.Admin.Application.UserSelectorOutput": {
"Account": "Account",
"OrgId": "Org"
},
"ThingsGateway.Admin.Application.VerificatInfo": {
"Device": "Device",
"Expire": "Expire(min)",
"LoginIp": "LoginIp",
"LoginTime": "LoginTime",
"Online": "Online",
"VerificatTimeout": "VerificatTimeout"
},
"ThingsGateway.Admin.Application.WebsitePolicy": {
"CloseTip": "CloseTip",
"CloseTip.Required": "{0} is required",
"WebStatus": "WebStatus"
},
"ThingsGateway.Admin.Application.WorkbenchInfo": {
"Razor": "Homepage",
"Shortcuts": "Shortcuts"
}
}
}

View File

@@ -1,462 +1,393 @@
{
"ThingsGateway.Admin.Application.BaseDataEntity": {
"CreateOrgId": "创建机构Id"
"ThingsGateway.Admin.Application.AppConfig": {
"LoginPolicy": "登录策略",
"PagePolicy": "页面设置",
"PasswordPolicy": "密码策略",
"WebsitePolicy": "网站设置"
},
"ThingsGateway.Admin.Application.BaseEntity": {
"SortCode": "排序",
"CreateTime": "创建时间",
"CreateUser": "创建人",
"UpdateTime": "更新时间",
"UpdateUser": "更新人"
"ThingsGateway.Admin.Application.AuthController": {
"AuthController": "登录API",
"LoginAsync": "登录",
"LogoutAsync": "注销"
},
"ThingsGateway.Admin.Application.AuthService": {
"AuthErrorMax": "账号密码错误,超过 {0} 次后将锁定 {1} 分钟,错误次数 {2} ",
"MustDesc": "密码需要DESC加密后传入",
"OrgDisable": "所属公司/部门已停用,请联系管理员",
"PasswordError": "密码错误次数过多,请 {0} 分钟后再试",
"SingleLoginWarn": "您的账号已在别处登录",
"TenantNull": "租户不存在",
"UserDisable": "账号 {0} 已停用",
"UserNoModule": "该账号未分配模块,请联系管理员",
"UserNull": "用户 {0} 不存在"
},
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
"UserExpire": "用户登录已过期,请重新登录"
},
"ThingsGateway.Admin.Application.SysUser": {
"Disable": "禁用",
"Enable": "启用",
"GrantRole": "分配角色",
"ExitVerificat": "您已被强制下线",
"PasswordEdited": "密码被修改,已退出登录",
"Avatar": "头像",
"Account": "账号",
"Account.Required": " {0} 是必填项",
"Password": "密码",
"Status": "状态",
"Phone": "手机",
"Email": "邮箱",
"LastLoginIp": "上次登录ip",
"LastLoginDevice": "上次登录设备",
"LastLoginTime": "上次登录时间",
"LastLoginAddress": "上次登录地点",
"LatestLoginIp": "最新登录ip",
"LatestLoginTime": "最新登录时间",
"LatestLoginDevice": "最新登录设备",
"LatestLoginAddress": "最新登录地点",
"OrgNames": "机构名称",
"PositionName": "职位名称",
"OrgId": "机构",
"PositionId": "职位",
"DirectorId": "主管",
"CheckSelf": "禁止 {0} 自己",
"CanotDeleteAdminUser": "不可删除系统内置超管用户",
"CanotEditAdminUser": "不可编辑超管用户",
"CanotGrantAdmin": "不能分配超管角色",
"EmailDup": "存在重复的邮箱 {0}",
"AccountDup": "存在重复的账号 {0}",
"CanotDeleteSelf": "不可删除自己",
"EmailError": "邮箱 {0} 格式错误",
"PhoneError": "手机号码 {0} 格式错误",
"NoOrg": "组织机构不存在",
"DirectorSelf": "不能设置自己为主管",
"DemoCanotUpdatePassword": "DEMO环境不允许修改密码",
"OldPasswordError": "原密码错误",
"ConfirmPasswordDiff": "两次输入的密码不一致",
"PasswordLengthLess": "密码长度不能小于 {0} ",
"PasswordMustNum ": "密码必须包含数字",
"PasswordMustLow": "密码必须包含小写字母",
"PasswordMustUpp": "密码必须包含大写字母",
"PasswordMustSpecial": "密码必须包含特殊字符"
},
"ThingsGateway.Admin.Application.SysRole": {
"Code": "编码",
"Name": "名称",
"Name.Required": " {0} 是必填项",
"Category": "分类",
"Global": "全局",
"Status": "状态",
"OrgId": "机构",
"CanotDeleteAdmin": "不可删除系统内置超管角色",
"CanotEditAdmin": "不可编辑超管角色",
"CanotGrantAdmin": "不能分配超管角色",
"NameDup": "存在重复的角色名称 {0}",
"OrgNotNull": "机构不能为空",
"SameOrgNameDup": "存在重复的角色名称 {0}",
"CannotRoleScopeAll": "机构角色不能选择全局数据范围",
"CodeDup": "存在重复的编码 {0}"
},
"ThingsGateway.Admin.Application.RoleCategoryEnum": {
"Global": "全局",
"Org": "机构"
},
"ThingsGateway.Admin.Application.DataScopeEnum": {
"SCOPE_SELF": "仅自己",
"SCOPE_ALL": "全部",
"SCOPE_ORG": "仅所属组织",
"SCOPE_ORG_CHILD": "所属组织及以下",
"SCOPE_ORG_DEFINE": "自定义"
"SCOPE_ORG_DEFINE": "自定义",
"SCOPE_SELF": "仅自己"
},
"ThingsGateway.Admin.Application.DefaultDataScope": {
"ScopeCategory": "数据范围",
"ScopeDefineOrgIdList": "自定义列表"
},
"ThingsGateway.Admin.Application.SysResource": {
"Title": "标题",
"Module": "模块",
"Title.Required": "{0} 是必填项",
"Href.Required": "{0} 是必填项",
"Icon": "图标",
"Href": "路径",
"Code": "编码",
"Category": "分类",
"Target": "跳转类型",
"NavLinkMatch": "匹配类型",
"ParentId": "上级菜单",
"ResourceDup": "存在重复的名称 {0}",
"ResourceParentChoiceSelf": "父级不能选择自己",
"ResourceParentNull": "父级不存在 {0}",
"NotFoundResource": "系统异常,没找到该菜单",
"ModuleIdDiff": "模块与上级菜单不一致",
"CanotDeleteSystemResource": "不可删除系统资源 {0}",
"ResourceMenuHrefNotNull": "菜单的路径不能为空"
"ThingsGateway.Admin.Application.DictTypeEnum": {
"Define": "业务配置",
"System": "系统配置"
},
"ThingsGateway.Admin.Application.SysOrgCopyInput": {
"TargetId": "目标机构",
"ContainsChild": "包含下级",
"ContainsPosition": "包含职位"
"ThingsGateway.Admin.Application.FileService": {
"FileLengthError": "文件大小不允许超过 {0} M",
"FileNullError": "文件不能为空",
"FileTypeError": "不支持 {0} 格式"
},
"ThingsGateway.Admin.Application.SysPosition": {
"Category.Required": "{0} 是必填项",
"Name.Required": "{0} 是必填项",
"Code.Required": "{0} 是必填项",
"OrgId.MinValue": "{0} 是必填项",
"Category": "分类",
"Name": "名称",
"Code": "代码",
"Status": "状态",
"OrgId": "机构",
"Remark": "备注",
"Dup": "存在重复的岗位 分类 {0} 名称 {1}",
"CodeDup": "存在重复的编码 {0}",
"NameDup": "存在重复的名称 {0}",
"CanotContainsSelf": "不可包含自己",
"TargetNameDup": "目标节点存在重复的名称 {0}",
"ParentChoiceSelf": "父级不能选择自己",
"ParentNull": "父级不存在 {0}",
"DeleteUserFirst": "请先删除职位下的用户"
"ThingsGateway.Admin.Application.HardwareInfo": {
"Environment": "主机环境",
"FrameworkDescription": "NET框架",
"OsArchitecture": "系统架构",
"UpdateTime": "更新时间",
"UUID": "唯一编码"
},
"ThingsGateway.Admin.Application.SysOrg": {
"Category.Required": "{0} 是必填项",
"Name.Required": "{0} 是必填项",
"Code.Required": "{0} 是必填项",
"ThingsGateway.Admin.Application.HardwareJob": {
"GetHardwareInfoFail": "获取硬件信息出错"
},
"ThingsGateway.Admin.Application.HistoryHardwareInfo": {
"Battery": "电池",
"CpuUsage": "CPU使用率",
"DriveUsage": "磁盘使用率",
"MemoryUsage": "内存",
"Temperature": "温度"
},
"ThingsGateway.Admin.Application.LogCateGoryEnum": {
"Exception": "异常",
"Login": "登录",
"Logout": "注销",
"Operate": "操作"
},
"ThingsGateway.Admin.Application.LogEnum": {
"FAIL": "失败",
"SUCCESS": "成功"
},
"ThingsGateway.Admin.Application.LoginInput": {
"Account": "登录账号",
"Account.Required": "{0} 是必填项",
"Password": "登录密码",
"Password.Required": "{0} 是必填项"
},
"ThingsGateway.Admin.Application.LoginPolicy": {
"ErrorCount": "登录错误次数锁定阈值",
"ErrorCount.MinValue": " {0} 值太小",
"ErrorLockTime": "登录错误锁定时长(分)",
"ErrorLockTime.MinValue": " {0} 值太小",
"ErrorResetTime": "登录错误次数过期时长(分)",
"ErrorResetTime.MinValue": " {0} 值太小",
"SingleOpen": "单用户登录开关",
"VerificatExpireTime": "登录过期时间(分)",
"VerificatExpireTime.MinValue": " {0} 值太小"
},
"ThingsGateway.Admin.Application.LogoutInput": {
"VerificatId.Required": "{0} 是必填项"
},
"ThingsGateway.Admin.Application.OpenApiAuthController": {
"LoginAsync": "登录",
"LogoutAsync": "注销",
"OpenApiAuthController": "登录API"
},
"ThingsGateway.Admin.Application.OperateLogPageInput": {
"Account": "操作账号",
"Category": "分类",
"Name": "名称",
"Code": "代码",
"Status": "状态",
"ParentId": "上级机构",
"Names": "机构全称",
"Remark": "备注",
"DirectorId": "主管",
"Dup": "存在重复的机构 分类 {0} 名称 {1}",
"CodeDup": "存在重复的编码 {0}",
"NameDup": "存在重复的名称 {0}",
"CanotContainsSelf": "不可包含自己",
"TargetNameDup": "目标节点存在重复的名称 {0}",
"ParentChoiceSelf": "父级不能选择自己",
"ParentNull": "父级不存在 {0}",
"DeleteUserFirst": "请先删除机构下的用户",
"DeleteRoleFirst": "请先删除机构下的角色",
"DeletePositionFirst": "请先删除机构下的职位",
"RootOrg": "无法创建顶层机构"
"SearchDate": "时间范围"
},
"ThingsGateway.Admin.Application.OperDescAttribute": {
"ChangeParentResource": "更改父节点",
"CopyOrg": "复制机构",
"CopyResource": "复制资源",
"DeleteDict": "删除字典",
"DeleteOperLog": "删除操作日志",
"DeleteOrg": "删除机构",
"DeletePosition": "删除岗位",
"DeleteResource": "删除资源",
"DeleteRole": "删除角色",
"DeleteuSER": "删除用户",
"EditLoginPolicy": "修改登录策略",
"EditPagePolicy": "修改页面策略",
"EditPasswordPolicy": "修改密码策略",
"EditWebsitePolicy": "修改网站设置",
"ExitSession": "强退会话",
"ExitVerificat": "强退令牌",
"ExportOperLog": "导出操作日志",
"GrantApi": "API",
"GrantResource": "资源",
"GrantRole": "角色",
"GrantUser": "用户",
"NoPermission": "无权限操作",
"ResetPassword": "重置密码",
"RoleGrantApiPermission": "角色授权OpenApi",
"RoleGrantResource": "角色授权资源",
"RoleGrantUser": "角色授权用户",
"SaveDict": "修改字典",
"SaveOrg": "保存机构",
"SavePosition": "保存岗位",
"SaveResource": "修改资源",
"SaveRole": "修改角色",
"SaveUser": "修改用户",
"UpdatePassword": "更新个人密码",
"UpdateUserInfo": "更新个人信息",
"UserGrantApiPermission": "用户授权OpenApi",
"UserGrantResource": "用户授权资源",
"UserGrantRole": "用户授权角色",
"WorkbenchInfo": "更新个人工作台"
},
"ThingsGateway.Admin.Application.OrgEnum": {
"COMPANY": "公司",
"DEPT": "部门"
},
"ThingsGateway.Admin.Application.PagePolicy": {
"Razor": "默认主页",
"Shortcuts": "默认快捷方式"
},
"ThingsGateway.Admin.Application.PasswordPolicy": {
"DefaultPassword": "默认用户密码",
"DefaultPassword.Required": " {0} 是必填项",
"PasswordContainChar": "包含特殊字符",
"PasswordContainLower": "包含小写字母",
"PasswordContainNum": "包含数字",
"PasswordContainUpper": "包含大写字母",
"PasswordMinLen": "密码最小长度",
"PasswordMinLen.MinValue": " {0} 值太小"
},
"ThingsGateway.Admin.Application.PositionCategoryEnum": {
"HIGH": "高层",
"MIDDLE": "层",
"LOW": "层"
"LOW": "层",
"MIDDLE": "层"
},
//controller
"ThingsGateway.Admin.Application.AuthController": {
//auth
"AuthController": "登录API",
"LoginAsync": "登录",
"LogoutAsync": "注销"
},
"ThingsGateway.Admin.Application.TestController": {
//auth
"TestController": "测试API",
"Test": "测试"
},
"ThingsGateway.Admin.Application.OpenApiAuthController": {
//auth
"OpenApiAuthController": "登录API",
"LoginAsync": "登录",
"LogoutAsync": "注销"
},
"ThingsGateway.Admin.Application.FileService": {
"FileNullError": "文件不能为空",
"FileLengthError": "文件大小不允许超过 {0} M",
"FileTypeError": "不支持 {0} 格式"
},
"ThingsGateway.Admin.Application.UnifyResultProvider": {
"TokenOver": "登录已过期,请重新登录",
"NoPermission": "禁止访问,没有权限"
},
"ThingsGateway.Admin.Application.AuthService": {
"TenantNull": "租户不存在",
"OrgDisable": "所属公司/部门已停用,请联系管理员",
"SingleLoginWarn": "您的账号已在别处登录",
"UserNull": "用户 {0} 不存在",
"PasswordError": "密码错误次数过多,请 {0} 分钟后再试",
"AuthErrorMax": "账号密码错误,超过 {0} 次后将锁定 {1} 分钟,错误次数 {2} ",
"UserDisable": "账号 {0} 已停用",
"MustDesc": "密码需要DESC加密后传入",
"UserNoModule": "该账号未分配模块,请联系管理员"
},
"ThingsGateway.Admin.Application.HardwareInfo": {
"Environment": "主机环境",
"FrameworkDescription": "NET框架",
"OsArchitecture": "系统架构",
"UUID": "唯一编码",
"UpdateTime": "更新时间"
},
"ThingsGateway.Admin.Application.HistoryHardwareInfo": {
"DriveUsage": "磁盘使用率",
"MemoryUsage": "内存",
"CpuUsage": "CPU使用率",
"Temperature": "温度",
"Battery": "电池"
},
//oper
"ThingsGateway.Admin.Application.OperDescAttribute": {
//dict
"SaveDict": "修改字典",
"DeleteDict": "删除字典",
"EditLoginPolicy": "修改登录策略",
"EditPasswordPolicy": "修改密码策略",
"EditPagePolicy": "修改页面策略",
"EditWebsitePolicy": "修改网站设置",
//operlog
"DeleteOperLog": "删除操作日志",
"ExportOperLog": "导出操作日志",
//resource
"SaveResource": "修改资源",
"DeleteResource": "删除资源",
//role
"SaveRole": "修改角色",
"DeleteRole": "删除角色",
"RoleGrantResource": "角色授权资源",
"RoleGrantUser": "角色授权用户",
"RoleGrantApiPermission": "角色授权OpenApi",
"GrantApi": "API",
"GrantUser": "用户",
"GrantRole": "角色",
"GrantResource": "资源",
//user
"SaveUser": "修改用户",
"DeleteuSER": "删除用户",
"ResetPassword": "重置密码",
"UserGrantRole": "用户授权角色",
"UserGrantResource": "用户授权资源",
"UserGrantApiPermission": "用户授权OpenApi",
//usercenter
"UpdateUserInfo": "更新个人信息",
"WorkbenchInfo": "更新个人工作台",
"UpdatePassword": "更新个人密码",
//session
"ExitVerificat": "强退令牌",
"ExitSession": "强退会话",
"CopyOrg": "复制机构",
"DeleteOrg": "删除机构",
"SaveOrg": "保存机构",
"DeletePosition": "删除岗位",
"SavePosition": "保存岗位",
"NoPermission": "无权限操作",
"CopyResource": "复制资源",
"ChangeParentResource": "更改父节点"
},
//service
"ThingsGateway.Admin.Application.HardwareJob": {
"GetHardwareInfoFail": "获取硬件信息出错"
},
//dto
"ThingsGateway.Admin.Application.UserSelectorOutput": {
"Account": "账号",
"OrgId": "机构"
"ThingsGateway.Admin.Application.ResourceCategoryEnum": {
"Button": "按钮",
"Menu": "菜单",
"Module": "模块"
},
"ThingsGateway.Admin.Application.ResourceTableSearchModel": {
"Module": "模块",
"Href": "路径",
"Module": "模块",
"Title": "标题"
},
"ThingsGateway.Admin.Application.WorkbenchInfo": {
"Razor": "主页",
"Shortcuts": "快捷方式"
"ThingsGateway.Admin.Application.RoleCategoryEnum": {
"Global": "全局",
"Org": "机构"
},
"ThingsGateway.Admin.Application.UpdatePasswordInput": {
"Password": "密码",
"NewPassword": "新密码",
"ConfirmPassword": "确认密码",
"Password.Required": " {0} 是必填项",
"NewPassword.Required": " {0} 是必填项",
"ConfirmPassword.Required": " {0} 是必填项"
},
"ThingsGateway.Admin.Application.VerificatInfo": {
"Expire": "过期时间(分)",
"Online": "在线状态",
"VerificatTimeout": "超时时间",
"Device": "登录设备",
"LoginIp": "登录IP",
"LoginTime": "登录时间"
},
"ThingsGateway.Admin.Application.SessionOutput": {
"Account": "账号",
"Online": "在线状态",
"LatestLoginIp": "最新登录ip",
"LatestLoginTime": "最新登录时间",
"Online": "在线状态",
"VerificatCount": "令牌数量"
},
"ThingsGateway.Admin.Application.SysDict": {
"Category.Required": "{0} 是必填项",
"Name.Required": "{0} 是必填项",
"Code.Required": "{0} 是必填项",
"Category": "分类",
"Name": "名称",
"Category.Required": "{0} 是必填项",
"Code": "代码",
"Remark": "备注",
"Code.Required": "{0} 是必填项",
"DemoCanotUpdateWebsitePolicy": "DEMO环境不允许修改网站设置",
"DictDup": "存在重复的配置 分类 {0} 名称 {1}",
"DemoCanotUpdateWebsitePolicy": "DEMO环境不允许修改网站设置"
"Name": "名称",
"Name.Required": "{0} 是必填项",
"Remark": "备注"
},
"ThingsGateway.Admin.Application.SysOperateLog": {
"Category": "日志分类",
"ClassName": "类名",
"ExeMessage": "具体消息",
"MethodName": "方法名称",
"ParamJson": "请求参数",
"ReqMethod": "请求方式",
"ReqUrl": "请求地址",
"ResultJson": "返回结果",
"Category": "日志分类",
"ExeStatus": "执行状态",
"MethodName": "方法名称",
"Name": "日志名称",
"OpAccount": "账号",
"OpBrowser": "浏览器",
"OpIp": "ip",
"OpOs": "系统",
"OpTime": "操作时间",
"ParamJson": "请求参数",
"ReqMethod": "请求方式",
"ReqUrl": "请求地址",
"ResultJson": "返回结果",
"VerificatId": "验证Id"
},
"ThingsGateway.Admin.Application.OperateLogPageInput": {
"SearchDate": "时间范围",
"Account": "操作账号",
"Category": "分类"
"ThingsGateway.Admin.Application.SysOrg": {
"CanotContainsSelf": "不可包含自己",
"Category": "分类",
"Category.Required": "{0} 是必填项",
"Code": "代码",
"Code.Required": "{0} 是必填项",
"CodeDup": "存在重复的编码 {0}",
"DeletePositionFirst": "请先删除机构下的职位",
"DeleteRoleFirst": "请先删除机构下的角色",
"DeleteUserFirst": "请先删除机构下的用户",
"DirectorId": "主管",
"Dup": "存在重复的机构 分类 {0} 名称 {1}",
"Name": "名称",
"Name.Required": "{0} 是必填项",
"NameDup": "存在重复的名称 {0}",
"Names": "机构全称",
"ParentChoiceSelf": "父级不能选择自己",
"ParentId": "上级机构",
"ParentNull": "父级不存在 {0}",
"Remark": "备注",
"RootOrg": "无法创建顶层机构",
"Status": "状态",
"TargetNameDup": "目标节点存在重复的名称 {0}"
},
"ThingsGateway.Admin.Application.LoginInput": {
"Account": "登录账号",
"Password": "登录密码",
"Account.Required": "{0} 是必填项",
"Password.Required": "{0} 是必填项"
"ThingsGateway.Admin.Application.SysOrgCopyInput": {
"ContainsChild": "包含下级",
"ContainsPosition": "包含职位",
"TargetId": "目标机构"
},
"ThingsGateway.Admin.Application.LogoutInput": {
"VerificatId.Required": "{0} 是必填项"
"ThingsGateway.Admin.Application.SysPosition": {
"CanotContainsSelf": "不可包含自己",
"Category": "分类",
"Category.Required": "{0} 是必填项",
"Code": "代码",
"Code.Required": "{0} 是必填项",
"CodeDup": "存在重复的编码 {0}",
"DeleteUserFirst": "请先删除职位下的用户",
"Dup": "存在重复的岗位 分类 {0} 名称 {1}",
"Name": "名称",
"Name.Required": "{0} 是必填项",
"NameDup": "存在重复的名称 {0}",
"OrgId": "机构",
"OrgId.MinValue": "{0} 是必填项",
"ParentChoiceSelf": "父级不能选择自己",
"ParentNull": "父级不存在 {0}",
"Remark": "备注",
"Status": "状态",
"TargetNameDup": "目标节点存在重复的名称 {0}"
},
"ThingsGateway.Admin.Application.AppConfig": {
"LoginPolicy": "登录策略",
"PasswordPolicy": "密码策略",
"PagePolicy": "页面设置",
"WebsitePolicy": "网站设置"
},
"ThingsGateway.Admin.Application.LoginPolicy": {
"SingleOpen": "单用户登录开关",
"ErrorLockTime": "登录错误锁定时长(分)",
"ErrorResetTime": "登录错误次数过期时长(分)",
"ErrorCount": "登录错误次数锁定阈值",
"VerificatExpireTime": "登录过期时间(分)",
"ErrorLockTime.MinValue": " {0} 值太小",
"ErrorResetTime.MinValue": " {0} 值太小",
"ErrorCount.MinValue": " {0} 值太小",
"VerificatExpireTime.MinValue": " {0} 值太小"
},
"ThingsGateway.Admin.Application.PagePolicy": {
"Shortcuts": "默认快捷方式",
"Razor": "默认主页"
},
"ThingsGateway.Admin.Application.PasswordPolicy": {
"DefaultPassword": "默认用户密码",
"DefaultPassword.Required": " {0} 是必填项",
"PasswordMinLen": "密码最小长度",
"PasswordMinLen.MinValue": " {0} 值太小",
"PasswordContainNum": "包含数字",
"PasswordContainLower": "包含小写字母",
"PasswordContainUpper": "包含大写字母",
"PasswordContainChar": "包含特殊字符"
},
"ThingsGateway.Admin.Application.WebsitePolicy": {
"WebStatus": "是否开放",
"CloseTip": "关闭提示",
"CloseTip.Required": " {0} 是必填项"
},
//enum
"ThingsGateway.Admin.Application.ResourceCategoryEnum": {
"ThingsGateway.Admin.Application.SysResource": {
"CanotDeleteSystemResource": "不可删除系统资源 {0}",
"Category": "分类",
"Code": "编码",
"Href": "路径",
"Href.Required": "{0} 是必填项",
"Icon": "图标",
"Module": "模块",
"Menu": "菜单",
"Button": "按钮"
"ModuleIdDiff": "模块与上级菜单不一致",
"NavLinkMatch": "匹配类型",
"NotFoundResource": "系统异常,没找到该菜单",
"ParentId": "上级菜单",
"ResourceDup": "存在重复的名称 {0}",
"ResourceMenuHrefNotNull": "菜单的路径不能为空",
"ResourceParentChoiceSelf": "父级不能选择自己",
"ResourceParentNull": "父级不存在 {0}",
"Target": "跳转类型",
"Title": "标题",
"Title.Required": "{0} 是必填项"
},
"ThingsGateway.Admin.Application.SysRole": {
"CannotRoleScopeAll": "机构角色不能选择全局数据范围",
"CanotDeleteAdmin": "不可删除系统内置超管角色",
"CanotEditAdmin": "不可编辑超管角色",
"CanotGrantAdmin": "不能分配超管角色",
"Category": "分类",
"Code": "编码",
"CodeDup": "存在重复的编码 {0}",
"Global": "全局",
"Name": "名称",
"Name.Required": " {0} 是必填项",
"NameDup": "存在重复的角色名称 {0}",
"OrgId": "机构",
"OrgNotNull": "机构不能为空",
"SameOrgNameDup": "存在重复的角色名称 {0}",
"Status": "状态"
},
"ThingsGateway.Admin.Application.SysUser": {
"Account": "账号",
"Account.Required": " {0} 是必填项",
"AccountDup": "存在重复的账号 {0}",
"Avatar": "头像",
"CanotDeleteAdminUser": "不可删除系统内置超管用户",
"CanotDeleteSelf": "不可删除自己",
"CanotEditAdminUser": "不可编辑超管用户",
"CanotGrantAdmin": "不能分配超管角色",
"CheckSelf": "禁止 {0} 自己",
"ConfirmPasswordDiff": "两次输入的密码不一致",
"DemoCanotUpdatePassword": "DEMO环境不允许修改密码",
"DirectorId": "主管",
"DirectorSelf": "不能设置自己为主管",
"Disable": "禁用",
"Email": "邮箱",
"EmailDup": "存在重复的邮箱 {0}",
"EmailError": "邮箱 {0} 格式错误",
"Enable": "启用",
"ExitVerificat": "您已被强制下线",
"GrantRole": "分配角色",
"LastLoginAddress": "上次登录地点",
"LastLoginDevice": "上次登录设备",
"LastLoginIp": "上次登录ip",
"LastLoginTime": "上次登录时间",
"LatestLoginAddress": "最新登录地点",
"LatestLoginDevice": "最新登录设备",
"LatestLoginIp": "最新登录ip",
"LatestLoginTime": "最新登录时间",
"NoOrg": "组织机构不存在",
"OldPasswordError": "原密码错误",
"OrgId": "机构",
"OrgNames": "机构名称",
"Password": "密码",
"PasswordEdited": "密码被修改,已退出登录",
"PasswordLengthLess": "密码长度不能小于 {0} ",
"PasswordMustLow": "密码必须包含小写字母",
"PasswordMustNum": "密码必须包含数字",
"PasswordMustSpecial": "密码必须包含特殊字符",
"PasswordMustUpp": "密码必须包含大写字母",
"Phone": "手机",
"PhoneError": "手机号码 {0} 格式错误",
"PositionId": "职位",
"PositionName": "职位名称",
"Status": "状态"
},
"ThingsGateway.Admin.Application.TargetEnum": {
"_self": "本窗口",
"_blank": "新窗口",
"_parent": "父级窗口",
"_self": "本窗口",
"_top": "顶级窗口"
},
"ThingsGateway.Admin.Application.DictTypeEnum": {
"System": "系统配置",
"Define": "业务配置"
"ThingsGateway.Admin.Application.TestController": {
"Test": "测试",
"TestController": "测试API"
},
"ThingsGateway.Admin.Application.LogCateGoryEnum": {
"Login": "登录",
"Logout": "注销",
"Operate": "操作",
"Exception": "异常"
"ThingsGateway.Admin.Application.UnifyResultProvider": {
"NoPermission": "禁止访问,没有权限",
"TokenOver": "登录已过期,请重新登录"
},
"ThingsGateway.Admin.Application.LogEnum": {
"SUCCESS": "成功",
"FAIL": "失败"
"ThingsGateway.Admin.Application.UpdatePasswordInput": {
"ConfirmPassword": "确认密码",
"ConfirmPassword.Required": " {0} 是必填项",
"NewPassword": "新密码",
"NewPassword.Required": " {0} 是必填项",
"Password": "密码",
"Password.Required": " {0} 是必填项"
},
"ThingsGateway.Admin.Application.UserSelectorOutput": {
"Account": "账号",
"OrgId": "机构"
},
"ThingsGateway.Admin.Application.VerificatInfo": {
"Device": "登录设备",
"Expire": "过期时间(分)",
"LoginIp": "登录IP",
"LoginTime": "登录时间",
"Online": "在线状态",
"VerificatTimeout": "超时时间"
},
"ThingsGateway.Admin.Application.WebsitePolicy": {
"CloseTip": "关闭提示",
"CloseTip.Required": " {0} 是必填项",
"WebStatus": "是否开放"
},
"ThingsGateway.Admin.Application.WorkbenchInfo": {
"Razor": "主页",
"Shortcuts": "快捷方式"
}
}
}

View File

@@ -8,17 +8,13 @@
// QQ群605534569
//------------------------------------------------------------------------------
using SqlSugar;
using System.Collections.Concurrent;
using System.Reflection;
using ThingsGateway.Extension;
using ThingsGateway.FriendlyException;
using ThingsGateway.Logging;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.Razor;
using UAParser;
namespace ThingsGateway.Admin.Application;
@@ -41,33 +37,36 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
/// <param name="flush"></param>
public async Task WriteAsync(LogMessage logMsg, bool flush)
{
//获取请求json字符串
var jsonString = logMsg.Context.Get("loggingMonitor").ToString();
//转成实体
var loggingMonitor = jsonString.FromJsonNetString<LoggingMonitorJson>();
var requestAuditData = logMsg.Context.Get(nameof(RequestAuditData)) as RequestAuditData;
//日志时间赋值
loggingMonitor.LogDateTime = logMsg.LogDateTime;
// loggingMonitor.ReturnInformation.Value
requestAuditData.LogDateTime = logMsg.LogDateTime;
// requestAuditData.ReturnInformation.Value
//验证失败不记录日志
bool save = false;
if (loggingMonitor.Validation == null)
if (requestAuditData.Validation == null)
{
var operation = logMsg.Context.Get(LoggingConst.Operation).ToString();//获取操作名称
var client = (ClientInfo)logMsg.Context.Get(LoggingConst.Client);//获取客户端信息
var path = logMsg.Context.Get(LoggingConst.Path).ToString();//获取操作名称
var method = logMsg.Context.Get(LoggingConst.Method).ToString();//获取方法
var operation = requestAuditData.Operation;//获取操作名称
var client = requestAuditData.Client;//获取客户端信息
var path = requestAuditData.Path;//获取操作名称
var method = requestAuditData.Method;//获取方法
var methodInfo = requestAuditData.MethodInfo;
var login = methodInfo.GetCustomAttribute(typeof(LoginLogAttribute));
var logout = methodInfo.GetCustomAttribute(typeof(LogoutLogAttribute));
//表示访问日志
if (path == "/api/auth/login" || path == "/api/auth/logout")
if (login != null || logout != null)
{
//如果没有异常信息
if (loggingMonitor.Exception == null)
if (requestAuditData.Exception == null)
{
save = await CreateVisitLog(operation, path, loggingMonitor, client, flush).ConfigureAwait(false);//添加到访问日志
LogCateGoryEnum logCateGoryEnum = login != null ? LogCateGoryEnum.Login : LogCateGoryEnum.Logout;
save = await CreateVisitLog(operation, path, requestAuditData, client, logCateGoryEnum, flush).ConfigureAwait(false);//添加到访问日志
}
else
{
//添加到异常日志
save = await CreateOperationLog(operation, path, loggingMonitor, client, flush).ConfigureAwait(false);
save = await CreateOperationLog(operation, path, requestAuditData, client, flush).ConfigureAwait(false);
}
}
else
@@ -76,7 +75,7 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
if (!operation.IsNullOrWhiteSpace() && method == "POST")
{
//添加到操作日志
save = await CreateOperationLog(operation, path, loggingMonitor, client, flush).ConfigureAwait(false);
save = await CreateOperationLog(operation, path, requestAuditData, client, flush).ConfigureAwait(false);
}
}
}
@@ -91,27 +90,20 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
/// </summary>
/// <param name="operation">操作名称</param>
/// <param name="path">请求地址</param>
/// <param name="loggingMonitor">loggingMonitor</param>
/// <param name="clientInfo">客户端信息</param>
/// <param name="requestAuditData">requestAuditData</param>
/// <param name="userAgent">客户端信息</param>
/// <param name="flush"></param>
/// <returns></returns>
private async Task<bool> CreateOperationLog(string operation, string path, LoggingMonitorJson loggingMonitor, ClientInfo clientInfo, bool flush)
private async Task<bool> CreateOperationLog(string operation, string path, RequestAuditData requestAuditData, UserAgent userAgent, bool flush)
{
//账号
var opAccount = loggingMonitor.AuthorizationClaims?.Where(it => it.Type == ClaimConst.Account).Select(it => it.Value).FirstOrDefault();
var opAccount = requestAuditData.AuthorizationClaims?.Where(it => it.Type == ClaimConst.Account).Select(it => it.Value).FirstOrDefault();
//获取参数json字符串
var paramJson = loggingMonitor.Parameters == null || loggingMonitor.Parameters.Count == 0 ? null : loggingMonitor.Parameters[0].Value.ToJsonNetString();
var paramJson = requestAuditData.Parameters == null || requestAuditData.Parameters.Count == 0 ? null : requestAuditData.Parameters.ToSystemTextJsonString();
//获取结果json字符串
var resultJson = string.Empty;
if (loggingMonitor.ReturnInformation != null)//如果有返回值
{
if (loggingMonitor.ReturnInformation.Value != null)//如果返回值不为空
{
resultJson = loggingMonitor.ReturnInformation.Value.ToJsonNetString();
}
}
var resultJson = requestAuditData.ReturnInformation?.ToSystemTextJsonString();
//操作日志表实体
var sysLogOperate = new SysOperateLog
@@ -119,36 +111,36 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
Name = operation,
Category = LogCateGoryEnum.Operate,
ExeStatus = true,
OpIp = loggingMonitor.RemoteIPv4,
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
OpTime = loggingMonitor.LogDateTime.LocalDateTime,
OpIp = requestAuditData.RemoteIPv4,
OpBrowser = userAgent?.Browser,
OpOs = userAgent?.Platform,
OpTime = requestAuditData.LogDateTime.LocalDateTime,
OpAccount = opAccount,
ReqMethod = loggingMonitor.HttpMethod,
ReqMethod = requestAuditData.Method,
ReqUrl = path,
ResultJson = resultJson,
ClassName = loggingMonitor.DisplayName,
MethodName = loggingMonitor.ActionName,
ClassName = requestAuditData.ControllerName,
MethodName = requestAuditData.ActionName,
ParamJson = paramJson,
VerificatId = UserManager.VerificatId,
};
//如果异常不为空
if (loggingMonitor.Exception != null)
if (requestAuditData.Exception != null)
{
sysLogOperate.Category = LogCateGoryEnum.Exception;//操作类型为异常
sysLogOperate.ExeStatus = false;//操作状态为失败
if (loggingMonitor.Exception.Type == typeof(AppFriendlyException).ToString())
sysLogOperate.ExeMessage = loggingMonitor?.Exception.Message;
if (requestAuditData.Exception.Type == typeof(AppFriendlyException).ToString())
sysLogOperate.ExeMessage = requestAuditData?.Exception.Message;
else
sysLogOperate.ExeMessage = $"{loggingMonitor.Exception.Type}:{loggingMonitor.Exception.Message}{Environment.NewLine}{loggingMonitor.Exception.StackTrace}";
sysLogOperate.ExeMessage = $"{requestAuditData.Exception.Type}:{requestAuditData.Exception.Message}{Environment.NewLine}{requestAuditData.Exception.StackTrace}";
}
_operateLogMessageQueue.Enqueue(sysLogOperate);
if (flush)
{
SqlSugarClient ??= DbContext.Db.GetConnectionScopeWithAttr<SysOperateLog>().CopyNew();
SqlSugarClient ??= DbContext.GetDB<SysOperateLog>();
await SqlSugarClient.InsertableWithAttr(_operateLogMessageQueue.ToListWithDequeue()).ExecuteCommandAsync().ConfigureAwait(false);//入库
return true;
}
@@ -160,52 +152,54 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
/// </summary>
/// <param name="operation">访问类型</param>
/// <param name="path"></param>
/// <param name="loggingMonitor">loggingMonitor</param>
/// <param name="clientInfo">客户端信息</param>
/// <param name="requestAuditData">requestAuditData</param>
/// <param name="userAgent">客户端信息</param>
/// <param name="logCateGoryEnum">logCateGory</param>
/// <param name="flush"></param>
private async Task<bool> CreateVisitLog(string operation, string path, LoggingMonitorJson loggingMonitor, ClientInfo clientInfo, bool flush)
private async Task<bool> CreateVisitLog(string operation, string path, RequestAuditData requestAuditData, UserAgent userAgent, LogCateGoryEnum logCateGoryEnum, bool flush)
{
long verificatId = 0;//验证Id
var opAccount = "";//用户账号
if (path == "/api/auth/login")
if (logCateGoryEnum == LogCateGoryEnum.Login)
{
//如果是登录,用户信息就从返回值里拿
var result = loggingMonitor.ReturnInformation?.Value?.ToJsonNetString();//返回值转json
var userInfo = result.FromJsonNetString<UnifyResult<LoginOutput>>();//格式化成user表
opAccount = userInfo.Data.Account;//赋值账号
verificatId = userInfo.Data.VerificatId;
if (requestAuditData.ReturnInformation is UnifyResult<LoginOutput> userInfo)
{
opAccount = userInfo.Data.Account;//赋值账号
verificatId = userInfo.Data.VerificatId;
}
}
else
{
//如果是登录出用户信息就从AuthorizationClaims里拿
opAccount = loggingMonitor.AuthorizationClaims.Where(it => it.Type == ClaimConst.Account).Select(it => it.Value).FirstOrDefault();
verificatId = loggingMonitor.AuthorizationClaims.Where(it => it.Type == ClaimConst.VerificatId).Select(it => it.Value).FirstOrDefault().ToLong();
opAccount = requestAuditData.AuthorizationClaims.Where(it => it.Type == ClaimConst.Account).Select(it => it.Value).FirstOrDefault();
verificatId = requestAuditData.AuthorizationClaims.Where(it => it.Type == ClaimConst.VerificatId).Select(it => it.Value).FirstOrDefault().ToLong();
}
//日志表实体
var sysLogVisit = new SysOperateLog
{
Name = operation,
Category = path == "/api/auth/login" ? LogCateGoryEnum.Login : LogCateGoryEnum.Logout,
Category = logCateGoryEnum,
ExeStatus = true,
OpIp = loggingMonitor.RemoteIPv4,
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
OpTime = loggingMonitor.LogDateTime.LocalDateTime,
OpIp = requestAuditData.RemoteIPv4,
OpBrowser = userAgent?.Browser,
OpOs = userAgent?.Platform,
OpTime = requestAuditData.LogDateTime.LocalDateTime,
VerificatId = verificatId,
OpAccount = opAccount,
ReqMethod = loggingMonitor.HttpMethod,
ReqMethod = requestAuditData.Method,
ReqUrl = path,
ResultJson = loggingMonitor.ReturnInformation?.Value?.ToJsonNetString(),
ClassName = loggingMonitor.DisplayName,
MethodName = loggingMonitor.ActionName,
ParamJson = loggingMonitor.Parameters?.ToJsonNetString(),
ResultJson = requestAuditData.ReturnInformation?.ToSystemTextJsonString(),
ClassName = requestAuditData.ControllerName,
MethodName = requestAuditData.ActionName,
ParamJson = requestAuditData.Parameters?.ToSystemTextJsonString(),
};
_operateLogMessageQueue.Enqueue(sysLogVisit);
if (flush)
{
SqlSugarClient ??= DbContext.Db.GetConnectionScopeWithAttr<SysOperateLog>().CopyNew();
SqlSugarClient ??= DbContext.GetDB<SysOperateLog>();
await SqlSugarClient.InsertableWithAttr(_operateLogMessageQueue.ToListWithDequeue()).ExecuteCommandAsync().ConfigureAwait(false);//入库
return true;
}

View File

@@ -13,7 +13,7 @@ namespace ThingsGateway.Admin.Application;
/// <summary>
/// 日志常量
/// </summary>
public class LoggingConst
public static class LoggingConst
{
/// <summary>
/// 分类

View File

@@ -0,0 +1,28 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Riok.Mapperly.Abstractions;
namespace ThingsGateway.Admin.Application;
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
public static partial class AdminMapper
{
public static partial LoginInput AdaptLoginInput(this OpenApiLoginInput src);
public static partial OpenApiLoginOutput AdaptOpenApiLoginOutput(this LoginOutput src);
public static partial SessionOutput AdaptSessionOutput(this SysUser src);
public static partial SysUser AdaptSysUser(this SysUser src);
public static partial UserSelectorOutput AdaptUserSelectorOutput(this SysUser src);
public static partial List<SysResource> AdaptListSysResource(this IEnumerable<SysResource> src);
public static partial AppConfig AdaptAppConfig(this AppConfig src);
public static partial WorkbenchInfo AdaptWorkbenchInfo(this WorkbenchInfo src);
public static partial QueryData<UserSelectorOutput> AdaptQueryDataUserSelectorOutput(this QueryData<SysUser> src);
public static partial LoginInput AdaptLoginInput(this LoginInput src);
}

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Admin.Application;
public class USheetDatas
{

View File

@@ -0,0 +1,228 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Security.Claims;
using System.Text.Encodings.Web;
using ThingsGateway.Extension;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 只适合 Demo 登录,会直接授权超管的权限
/// </summary>
public class AdminOAuthHandler<TOptions>(
IVerificatInfoService verificatInfoService,
IAppService appService,
ISysUserService sysUserService,
ISysDictService configService,
IOptionsMonitor<TOptions> options,
ILoggerFactory logger,
IUserAgentService userAgentService,
UrlEncoder encoder
) : OAuthHandler<TOptions>(options, logger, encoder)
where TOptions : AdminOAuthOptions, new()
{
static AdminOAuthHandler()
{
Task.Factory.StartNew(InsertableAsync, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
}
/// <summary>
/// 日志消息队列(线程安全)
/// </summary>
protected static readonly ConcurrentQueue<SysOperateLog> _operateLogMessageQueue = new();
/// <summary>
/// 创建访问日志
/// </summary>
private static async Task InsertableAsync()
{
var db = DbContext.GetDB<SysOperateLog>();
var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!;
while (!appLifetime.ApplicationStopping.IsCancellationRequested)
{
try
{
var data = _operateLogMessageQueue.ToListWithDequeue(); // 从日志队列中获取数据
if (data.Count > 0)
{
await db.InsertableWithAttr(data).ExecuteCommandAsync(appLifetime.ApplicationStopping).ConfigureAwait(false);//入库
}
}
catch (Exception ex)
{
NewLife.Log.XTrace.WriteException(ex);
}
finally
{
await Task.Delay(3000, appLifetime.ApplicationStopping).ConfigureAwait(false);
}
}
}
protected override async Task<AuthenticationTicket> CreateTicketAsync(
ClaimsIdentity identity,
AuthenticationProperties properties,
OAuthTokenResponse tokens)
{
Backchannel.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokens.AccessToken);
properties.RedirectUri = Options.HomePath;
properties.IsPersistent = true;
var appConfig = await configService.GetAppConfigAsync().ConfigureAwait(false);
int expire = appConfig.LoginPolicy.VerificatExpireTime;
if (!string.IsNullOrEmpty(tokens.ExpiresIn) && int.TryParse(tokens.ExpiresIn, out var result))
{
properties.ExpiresUtc = TimeProvider.System.GetUtcNow().AddSeconds(result);
expire = (int)(result / 60.0);
}
var user = await Options.HandleUserInfoAsync(Context, tokens).ConfigureAwait(false);
var loginEvent = await GetLogin(expire).ConfigureAwait(false);
await UpdateUser(loginEvent).ConfigureAwait(false);
identity.AddClaim(new Claim(ClaimConst.VerificatId, loginEvent.VerificatId.ToString()));
identity.AddClaim(new Claim(ClaimConst.UserId, RoleConst.SuperAdminId.ToString()));
identity.AddClaim(new Claim(ClaimConst.SuperAdmin, "true"));
identity.AddClaim(new Claim(ClaimConst.OrgId, RoleConst.DefaultTenantId.ToString()));
identity.AddClaim(new Claim(ClaimConst.TenantId, RoleConst.DefaultTenantId.ToString()));
var context = new OAuthCreatingTicketContext(
new ClaimsPrincipal(identity),
properties,
Context,
Scheme,
Options,
Backchannel,
tokens,
user
);
context.RunClaimActions();
await Events.CreatingTicket(context).ConfigureAwait(false);
var httpContext = context.HttpContext;
UserAgent? userAgent = null;
var str = httpContext?.Request?.Headers?.UserAgent;
if (!string.IsNullOrEmpty(str))
{
userAgent = userAgentService.Parse(str);
}
var sysOperateLog = new SysOperateLog()
{
Name = this.Scheme.Name,
Category = LogCateGoryEnum.Login,
ExeStatus = true,
OpIp = httpContext.GetRemoteIpAddressToIPv4(),
OpBrowser = userAgent?.Browser,
OpOs = userAgent?.Platform,
OpTime = DateTime.Now,
VerificatId = loginEvent.VerificatId,
OpAccount = Options.GetName(user),
ReqMethod = "OAuth",
ReqUrl = string.Empty,
ResultJson = string.Empty,
ClassName = nameof(AdminOAuthHandler<TOptions>),
MethodName = string.Empty,
ParamJson = string.Empty,
};
_operateLogMessageQueue.Enqueue(sysOperateLog);
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
}
private async Task<LoginEvent> GetLogin(int expire)
{
var sysUser = await sysUserService.GetUserByIdAsync(RoleConst.SuperAdminId).ConfigureAwait(false);//获取用户信息
var loginEvent = new LoginEvent
{
Ip = appService.RemoteIpAddress,
Device = appService.UserAgent?.Platform ?? "Unknown",
Expire = expire,
SysUser = sysUser,
VerificatId = CommonUtils.GetSingleId()
};
//获取verificat列表
var tokenTimeout = loginEvent.DateTime.AddMinutes(loginEvent.Expire);
//生成verificat信息
var verificatInfo = new VerificatInfo
{
Device = loginEvent.Device ?? "Unknown",
Expire = loginEvent.Expire,
VerificatTimeout = tokenTimeout,
Id = loginEvent.VerificatId,
UserId = loginEvent.SysUser.Id,
LoginIp = loginEvent.Ip,
LoginTime = loginEvent.DateTime
};
//添加到verificat列表
verificatInfoService.Add(verificatInfo);
return loginEvent;
}
/// <summary>
/// 登录事件
/// </summary>
/// <param name="loginEvent"></param>
/// <returns></returns>
private async Task UpdateUser(LoginEvent loginEvent)
{
var sysUser = loginEvent.SysUser;
#region /
var key = CacheConst.Cache_LoginErrorCount + sysUser.Account;//获取登录错误次数Key值
App.CacheService.Remove(key);//移除登录错误次数
//获取用户verificat列表
var userToken = verificatInfoService.GetOne(loginEvent.VerificatId);
#endregion /
#region ,
sysUser.LastLoginIp = sysUser.LatestLoginIp;
sysUser.LastLoginTime = sysUser.LatestLoginTime;
sysUser.LatestLoginIp = loginEvent.Ip;
sysUser.LatestLoginTime = loginEvent.DateTime;
#endregion ,
using var db = DbContext.GetDB<SysUser>();
//更新用户登录信息
if (await db.UpdateableT(sysUser).UpdateColumns(it => new
{
it.LastLoginIp,
it.LastLoginTime,
it.LatestLoginIp,
it.LatestLoginTime,
}).ExecuteCommandAsync().ConfigureAwait(false) > 0)
App.CacheService.HashAdd(CacheConst.Cache_SysUser, sysUser.Id.ToString(), sysUser);//更新Cache信息
}
}
/// <summary>自定义 Token 异常</summary>
public class OAuthTokenException : Exception
{
public OAuthTokenException() : base()
{
}
public OAuthTokenException(string? message, Exception? innerException) : base(message, innerException)
{
}
public OAuthTokenException(string? message) : base(message)
{
}
}

View File

@@ -0,0 +1,84 @@
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
namespace ThingsGateway.Admin.Application;
/// <summary>OAuthOptions 配置类</summary>
public abstract class AdminOAuthOptions : OAuthOptions
{
/// <summary>默认构造函数</summary>
protected AdminOAuthOptions()
{
ConfigureClaims();
this.Events.OnRemoteFailure = context =>
{
var redirectUri = string.IsNullOrEmpty(HomePath) ? "/" : HomePath;
context.Response.Redirect(redirectUri);
context.HandleResponse();
return Task.CompletedTask;
};
Backchannel = new HttpClient(new HttpClientHandler
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
});
Backchannel.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("ThingsGateway", "1.0"));
}
/// <summary>配置 Claims 映射</summary>
protected virtual void ConfigureClaims()
{
}
public virtual string GetName(JsonElement element)
{
JsonElement.ObjectEnumerator target = element.EnumerateObject();
return target.TryGetValue("name");
}
/// <summary>获得/设置 登陆后首页</summary>
public string HomePath { get; set; } = "/";
/// <summary>处理用户信息方法</summary>
public virtual async Task<JsonElement> HandleUserInfoAsync(HttpContext context, OAuthTokenResponse tokens)
{
var request = new HttpRequestMessage(HttpMethod.Get, BuildUserInfoUrl(tokens));
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await Backchannel.SendAsync(request, context.RequestAborted).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return JsonDocument.Parse(content).RootElement;
}
throw new OAuthTokenException($"OAuth user info endpoint failure: {await Display(response).ConfigureAwait(false)}");
}
/// <summary>生成用户信息请求地址方法</summary>
protected virtual string BuildUserInfoUrl(OAuthTokenResponse tokens)
{
return QueryHelpers.AddQueryString(UserInformationEndpoint, new Dictionary<string, string>
{
{ "access_token", tokens.AccessToken }
});
}
/// <summary>生成错误信息方法</summary>
protected async Task<string> Display(HttpResponseMessage response)
{
var output = new StringBuilder();
output.Append($"Status: {response.StatusCode}; ");
output.Append($"Headers: {response.Headers}; ");
output.Append($"Body: {await response.Content.ReadAsStringAsync().ConfigureAwait(false)};");
return output.ToString();
}
}

View File

@@ -0,0 +1,115 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.WebUtilities;
using System.Net.Http.Headers;
using System.Text.Json;
using ThingsGateway.NewLife.Log;
namespace ThingsGateway.Admin.Application;
public class GiteeOAuthOptions : AdminOAuthOptions
{
INoticeService _noticeService;
IVerificatInfoService _verificatInfoService;
public GiteeOAuthOptions() : base()
{
_noticeService = App.GetService<INoticeService>();
_verificatInfoService = App.GetService<IVerificatInfoService>();
this.SignInScheme = ClaimConst.Scheme;
this.AuthorizationEndpoint = "https://gitee.com/oauth/authorize";
this.TokenEndpoint = "https://gitee.com/oauth/token";
this.UserInformationEndpoint = "https://gitee.com/api/v5/user";
this.HomePath = "/";
this.CallbackPath = "/signin-gitee";
Scope.Add("user_info");
Scope.Add("projects");
Events.OnCreatingTicket = async context => await HandlerGiteeStarredUrl(context).ConfigureAwait(false);
Events.OnRedirectToAuthorizationEndpoint = context =>
{
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
};
Events.OnRemoteFailure = context =>
{
XTrace.WriteException(context.Failure);
return Task.CompletedTask;
};
}
/// <summary>刷新 Token 方法</summary>
protected virtual async Task<OAuthTokenResponse> RefreshTokenAsync(TicketReceivedContext ticketReceivedContext, string refreshToken)
{
var query = new Dictionary<string, string>
{
{ "refresh_token", refreshToken },
{ "grant_type", "refresh_token" }
};
var request = new HttpRequestMessage(HttpMethod.Post, QueryHelpers.AddQueryString(TokenEndpoint, query));
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await Backchannel.SendAsync(request, ticketReceivedContext.HttpContext.RequestAborted).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return OAuthTokenResponse.Success(JsonDocument.Parse(content));
}
return OAuthTokenResponse.Failed(new OAuthTokenException($"OAuth token endpoint failure: {await Display(response).ConfigureAwait(false)}"));
}
public override string GetName(JsonElement element)
{
JsonElement.ObjectEnumerator target = element.EnumerateObject();
return target.TryGetValue("name");
}
private async Task HandlerGiteeStarredUrl(OAuthCreatingTicketContext context, string repoFullName = "ThingsGateway/ThingsGateway")
{
if (string.IsNullOrWhiteSpace(context.AccessToken))
throw new InvalidOperationException("Access token is missing.");
var uri = $"https://gitee.com/api/v5/user/starred/{repoFullName}";
var queryString = new Dictionary<string, string>
{
{ "access_token", context.AccessToken }
};
var request = new HttpRequestMessage(HttpMethod.Get, QueryHelpers.AddQueryString(uri, queryString))
{
Headers = { Accept = { new MediaTypeWithQualityHeaderValue("application/json") } }
};
var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var id = context.Identity.Claims.FirstOrDefault(a => a.Type == ClaimConst.VerificatId).Value;
var verificatInfoIds = _verificatInfoService.GetOne(id.ToLong());
_ = Task.Run(async () =>
{
await Task.Delay(5000).ConfigureAwait(false);
await _noticeService.NavigationMesage(verificatInfoIds.ClientIds, "https://gitee.com/ThingsGateway/ThingsGateway", "创作不易如有帮助请star仓库").ConfigureAwait(false);
});
//throw new Exception($"Failed to star repository: {response.StatusCode}, {content}");
}
}
protected override void ConfigureClaims()
{
ClaimActions.MapJsonKey(ClaimConst.AvatarUrl, "avatar_url");
ClaimActions.MapJsonKey(ClaimConst.Account, "name");
base.ConfigureClaims();
}
}

View File

@@ -0,0 +1,7 @@
namespace ThingsGateway.Admin.Application;
public class GiteeOAuthSettings
{
public string ClientId { get; set; }
public string ClientSecret { get; set; }
}

View File

@@ -0,0 +1,112 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using ThingsGateway.NewLife.Log;
namespace ThingsGateway.Admin.Application;
public class GitHubOAuthOptions : AdminOAuthOptions
{
INoticeService _noticeService;
IVerificatInfoService _verificatInfoService;
public GitHubOAuthOptions() : base()
{
_noticeService = App.GetService<INoticeService>();
_verificatInfoService = App.GetService<IVerificatInfoService>();
SignInScheme = ClaimConst.Scheme;
AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
TokenEndpoint = "https://github.com/login/oauth/access_token";
UserInformationEndpoint = "https://api.github.com/user";
HomePath = "/";
CallbackPath = "/signin-github";
Scope.Add("read:user");
Scope.Add("public_repo"); // 需要用于 Star 仓库
Events.OnCreatingTicket = async context => await HandleGitHubStarAsync(context).ConfigureAwait(false);
Events.OnRedirectToAuthorizationEndpoint = context =>
{
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
};
Events.OnRemoteFailure = context =>
{
XTrace.WriteException(context.Failure);
return Task.CompletedTask;
};
}
protected override void ConfigureClaims()
{
ClaimActions.MapJsonKey(ClaimConst.AvatarUrl, "avatar_url");
ClaimActions.MapJsonKey(ClaimConst.Account, "login");
base.ConfigureClaims();
}
public override string GetName(JsonElement element)
{
if (element.TryGetProperty("login", out var loginProp))
{
return loginProp.GetString() ?? string.Empty;
}
return string.Empty;
}
private async Task HandleGitHubStarAsync(OAuthCreatingTicketContext context, string repoFullName = "ThingsGateway/ThingsGateway")
{
if (string.IsNullOrWhiteSpace(context.AccessToken))
throw new InvalidOperationException("Access token is missing.");
var request = new HttpRequestMessage(HttpMethod.Put, $"https://api.github.com/user/starred/{repoFullName}")
{
Headers =
{
Accept = { new MediaTypeWithQualityHeaderValue("application/vnd.github+json") },
Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken),
},
Content = new StringContent(string.Empty) // GitHub Star 接口需要空内容
};
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("ThingsGateway", "1.0")); // GitHub API 要求 User-Agent
var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var id = context.Identity.Claims.FirstOrDefault(a => a.Type == ClaimConst.VerificatId).Value;
var verificatInfoIds = _verificatInfoService.GetOne(id.ToLong());
_ = Task.Run(async () =>
{
await Task.Delay(5000).ConfigureAwait(false);
await _noticeService.NavigationMesage(verificatInfoIds.ClientIds, "https://github.com/ThingsGateway/ThingsGateway", "创作不易如有帮助请star仓库").ConfigureAwait(false);
});
}
}
/// <summary>处理用户信息方法</summary>
public override async Task<JsonElement> HandleUserInfoAsync(HttpContext context, OAuthTokenResponse tokens)
{
var request = new HttpRequestMessage(HttpMethod.Get, UserInformationEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github+json"));
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("ThingsGateway", "1.0")); // GitHub API 要求 User-Agent
var response = await Backchannel.SendAsync(request, context.RequestAborted).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return JsonDocument.Parse(content).RootElement;
}
throw new OAuthTokenException($"OAuth user info endpoint failure: {await Display(response).ConfigureAwait(false)}");
}
}

View File

@@ -0,0 +1,5 @@
namespace ThingsGateway.Admin.Application;
public class GithubOAuthSettings : GiteeOAuthSettings
{
}

View File

@@ -0,0 +1,11 @@
using System.Text.Json;
namespace ThingsGateway.Admin.Application;
public static class OAuthUserExtensions
{
public static string TryGetValue(this JsonElement.ObjectEnumerator target, string propertyName)
{
return target.FirstOrDefault<JsonProperty>((Func<JsonProperty, bool>)(t => t.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase))).Value.ToString() ?? string.Empty;
}
}

View File

@@ -12,10 +12,7 @@ using ThingsGateway.ConfigurableOptions;
namespace ThingsGateway.Admin.Application;
public sealed class AdminLogOptions : IConfigurableOptions
{
public int OperateLogDaysAgo { get; set; }
}

View File

@@ -12,7 +12,6 @@ using ThingsGateway.ConfigurableOptions;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 邮件配置选项
/// </summary>

View File

@@ -12,12 +12,10 @@ using ThingsGateway.ConfigurableOptions;
namespace ThingsGateway.Admin.Application;
public sealed class TenantOptions : IConfigurableOptions
{
/// <summary>
/// 启用
/// </summary>
public bool Enable { get; set; }
}

View File

@@ -10,7 +10,6 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
@@ -53,7 +52,6 @@ public class BlazorAuthenticationHandler : AppAuthorizeHandler
{
await Fail(context).ConfigureAwait(false);// 授权失败
}
}
static async Task Fail(AuthorizationHandlerContext context)
{
@@ -87,7 +85,7 @@ public class BlazorAuthenticationHandler : AppAuthorizeHandler
var roles = await _sysRoleService.GetRoleListByUserIdAsync(userId).ConfigureAwait(false);
//这里鉴别用户使能状态
if (user == null || !user.Status)
if (user?.Status != true)
{
return false;
}
@@ -116,7 +114,7 @@ public class BlazorAuthenticationHandler : AppAuthorizeHandler
{
// 路由名称
var routeName = routeData.PageType.CustomAttributes.FirstOrDefault(x =>
x.AttributeType == typeof(RouteAttribute))?.ConstructorArguments?[0].Value as string;
x.AttributeType == typeof(Microsoft.AspNetCore.Components.RouteAttribute))?.ConstructorArguments?[0].Value as string;
if (routeName == null) return true;
if ((!user.PermissionCodeList.Contains(routeName.CutStart("/")) && !user.PermissionCodeList.Contains(routeName))) //如果当前路由信息不包含在角色授权路由列表中则认证失败
@@ -137,7 +135,7 @@ public class BlazorAuthenticationHandler : AppAuthorizeHandler
else
{
//这里鉴别用户使能状态
if (user == null || !user.Status)
if (user?.Status != true)
{
return false;
}
@@ -264,15 +262,12 @@ public class BlazorAuthenticationHandler : AppAuthorizeHandler
{
verificatInfo.VerificatTimeout = verificatInfo.VerificatTimeout.AddMinutes(verificatInfo.Expire); //新的过期时间
VerificatInfoService.Update(verificatInfo); //更新tokne信息到cache
}
//无法在server中刷新cookies单页面应用会一直保持登录状态所以这里不需要刷新cookies但是F5刷新后会重新登录
}
return true;
}
else

View File

@@ -13,9 +13,7 @@ namespace ThingsGateway.Admin.Application;
/// <inheritdoc/>
public class BlazorHybridAuthorizationHandler : BlazorAuthenticationHandler
{
public BlazorHybridAuthorizationHandler(ISysUserService sysUserService, ISysRoleService sysRoleService, ISysDictService sysDictService) : base(sysUserService, sysRoleService, sysDictService)
{
}
}

View File

@@ -11,11 +11,9 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Localization;
using ThingsGateway.DataValidation;
using ThingsGateway.FriendlyException;
using ThingsGateway.Razor;
using ThingsGateway.UnifyResult;
namespace ThingsGateway.Admin.Application;
@@ -59,8 +57,6 @@ public class UnifyResultProvider : IUnifyResultProvider
return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data));
}
/// <summary>
/// 返回 RESTful 风格结果集
/// </summary>
@@ -97,6 +93,4 @@ public class UnifyResultProvider : IUnifyResultProvider
return new JsonResult(RESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, errors: metadata.ValidationResult) // 如果需要只显示第一条错误修改为errors: metadata.FirstErrorMessage
, UnifyContext.GetSerializerSettings(context));
}
}

View File

@@ -26,7 +26,7 @@
"Module": 2,
"Title": "权限管理",
"Code": "System",
"NavLinkMatch": "All",
"NavLinkMatch": "Prefix",
"Category": "MENU",
"Target": "_self",
"Href": null,
@@ -47,7 +47,7 @@
"ParentId": 0,
"Module": 2,
"Title": "系统运维",
"NavLinkMatch": "All",
"NavLinkMatch": "Prefix",
"Code": "System",
"Category": "MENU",
"Target": "_self",

View File

@@ -23,6 +23,4 @@ public class SysDictSeedData : ISqlSugarEntitySeedData<SysDict>
var assembly = GetType().Assembly;
return SeedDataUtil.GetSeedDataByJson<SysDict>(SeedDataUtil.GetManifestResourceStream(assembly, "SeedData.Admin.seed_sys_dict.json")).Concat(data);
}
}

View File

@@ -37,6 +37,5 @@ public class SysOrgSeedData : ISqlSugarEntitySeedData<SysOrg>
SortCode=0
}
}.Concat(SeedDataUtil.GetSeedDataByJson<SysOrg>(SeedDataUtil.GetManifestResourceStream(assembly, "SeedData.Admin.seed_sys_org.json")).Concat(data));
}
}

View File

@@ -36,6 +36,5 @@ public class SysPositionSeedData : ISqlSugarEntitySeedData<SysPosition>
SortCode=0
}
}.Concat(SeedDataUtil.GetSeedDataByJson<SysPosition>(SeedDataUtil.GetManifestResourceStream(assembly, "SeedData.Admin.seed_sys_position.json")).Concat(data));
}
}

View File

@@ -18,7 +18,7 @@ public class SysRelationSeedData : ISqlSugarEntitySeedData<SysRelation>
/// <inheritdoc/>
public IEnumerable<SysRelation> SeedData()
{
var db = DbContext.Db.GetConnectionScopeWithAttr<SysRelation>().CopyNew();
using var db = DbContext.GetDB<SysRelation>();
if (db.Queryable<SysRelation>().Any(a => a.ObjectId == RoleConst.SuperAdminId))
return Enumerable.Empty<SysRelation>();
var data = SeedDataUtil.GetSeedData<SysRelation>(PathExtensions.CombinePathWithOs("SeedData", "Admin", "seed_sys_relation.json"));

View File

@@ -31,6 +31,5 @@ public class SysResourceSeedData : ISqlSugarEntitySeedData<SysResource>
.Concat(data2)
.Concat(data3)
;
}
}

View File

@@ -35,6 +35,4 @@ public class SysUserSeedData : ISqlSugarEntitySeedData<SysUser>
}
}.Concat(SeedDataUtil.GetSeedDataByJson<SysUser>(SeedDataUtil.GetManifestResourceStream(assembly, "SeedData.Admin.seed_sys_user.json"))).Concat(data);
}
}

View File

@@ -8,20 +8,15 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Globalization;
using System.Reflection;
using ThingsGateway.Extension;
using ThingsGateway.Common.Extension;
namespace ThingsGateway.Admin.Application;
@@ -67,10 +62,8 @@ internal sealed class ApiPermissionService : IApiPermissionService
//foreach (var groupOpenApi in groupOpenApis)
{
foreach (var apiDescriptionGroup in apiDescriptions)
{
var routes = apiDescriptionGroup.Items.Where(api => api.ActionDescriptor is ControllerActionDescriptor);
Dictionary<string, OpenApiPermissionTreeSelector> openApiPermissionTreeSelectorDict = new();
@@ -84,7 +77,6 @@ internal sealed class ApiPermissionService : IApiPermissionService
if (openApiPermissionTreeSelectorDict.TryGetValue(actionDesc.ControllerName, out var openApiControllerGroup))
{
}
else
{
@@ -108,12 +100,9 @@ internal sealed class ApiPermissionService : IApiPermissionService
});
}
if (openApiPermissionTreeSelectorDict.Values.Any(a => a.Children.Count > 0))
permissions.AddRange(openApiPermissionTreeSelectorDict.Values);
}
}
App.CacheService.Set(cacheKey, permissions);
@@ -121,7 +110,6 @@ internal sealed class ApiPermissionService : IApiPermissionService
return permissions;
}
/// <summary>
/// 获取路由地址名称
/// </summary>

View File

@@ -8,7 +8,6 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using ThingsGateway.Admin.Application;
public interface IApiPermissionService

View File

@@ -15,12 +15,17 @@ using Microsoft.AspNetCore.WebUtilities;
using System.Security.Claims;
using UAParser;
namespace ThingsGateway.Admin.Application;
public class AppService : IAppService
{
private readonly IUserAgentService UserAgentService;
private readonly IClaimsPrincipalService ClaimsPrincipalService;
public AppService(IUserAgentService userAgentService, IClaimsPrincipalService claimsPrincipalService)
{
UserAgentService = userAgentService;
ClaimsPrincipalService = claimsPrincipalService;
}
public string GetReturnUrl(string returnUrl)
{
var url = QueryHelpers.AddQueryString(CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
@@ -41,23 +46,19 @@ public class AppService : IAppService
{
}
}
public Parser Parser = Parser.GetDefault();
public ClientInfo? ClientInfo
public UserAgent? UserAgent
{
get
{
var str = App.HttpContext?.Request?.Headers?.UserAgent;
ClientInfo? clientInfo = null;
if (!string.IsNullOrEmpty(str))
{
clientInfo = Parser.Parse(str);
return UserAgentService.Parse(str);
}
return clientInfo;
return null;
}
}
public async Task LoginAsync(ClaimsIdentity identity, int expire)
{
var diffTime = DateTime.Now + TimeSpan.FromMinutes(expire);
@@ -69,7 +70,7 @@ public class AppService : IAppService
ExpiresUtc = diffTime,
}).ConfigureAwait(false);
}
public ClaimsPrincipal? User => App.User;
public ClaimsPrincipal? User => ClaimsPrincipalService.User;
public string? RemoteIpAddress => App.HttpContext?.GetRemoteIpAddressToIPv4();

View File

@@ -13,19 +13,17 @@ using Microsoft.Extensions.DependencyInjection;
using System.Security.Claims;
using UAParser;
namespace ThingsGateway.Admin.Application;
public class HybridAppService : IAppService
{
public HybridAppService()
public HybridAppService(IUserAgentService userAgentService)
{
var str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0";
ClientInfo = Parser.GetDefault().Parse(str);
UserAgent = userAgentService.Parse(str);
RemoteIpAddress = "127.0.0.1";
}
public ClientInfo? ClientInfo { get; }
public UserAgent? UserAgent { get; }
private static BlazorHybridAuthenticationStateProvider _authenticationStateProvider;
private static BlazorHybridAuthenticationStateProvider AuthenticationStateProvider
@@ -73,5 +71,4 @@ public class HybridAppService : IAppService
User = new ClaimsPrincipal(claimsIdentity);
return Task.CompletedTask;
}
}

View File

@@ -8,14 +8,16 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Razor;
using System.Security.Claims;
public abstract class ThingsGatewayRulesEngineComponentBase : BootstrapModuleComponentBase
namespace ThingsGateway.Admin.Application;
public class HybridClaimsPrincipalService : IClaimsPrincipalService
{
/// <inheritdoc/>
protected override void OnLoadJSModule()
HybridAppService _hybridAppService;
public HybridClaimsPrincipalService(HybridAppService hybridAppService)
{
base.OnLoadJSModule();
ModulePath = $"./_content/ThingsGateway.RulesEngine/{ModulePath}";
_hybridAppService = hybridAppService;
}
public ClaimsPrincipal? User => _hybridAppService.User;
}

View File

@@ -8,11 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Security.Claims;
using UAParser;
namespace ThingsGateway.Admin.Application;
public interface IAppService
@@ -20,7 +17,7 @@ public interface IAppService
/// <summary>
/// ClientInfo
/// </summary>
public ClientInfo? ClientInfo { get; }
public UserAgent? UserAgent { get; }
/// <summary>
/// ClaimsPrincipal
@@ -49,7 +46,4 @@ public interface IAppService
/// </summary>
/// <returns></returns>
public Task LoginAsync(ClaimsIdentity claimsIdentity, int expire);
}

View File

@@ -10,9 +10,6 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Localization;
using SqlSugar;
using System.Security.Claims;
@@ -64,6 +61,7 @@ public class AuthService : IAuthService
{
throw Oops.Bah(appConfig.WebsitePolicy.CloseTip);
}
string? password = input.Password;
if (isCookie) //openApi登录不再需要解密
{
@@ -237,25 +235,20 @@ public class AuthService : IAuthService
var logingEvent = new LoginEvent
{
Ip = _appService.RemoteIpAddress,
Device = App.GetService<IAppService>().ClientInfo?.OS?.ToString(),
Device = _appService.UserAgent?.Platform ?? "Unknown",
Expire = expire,
SysUser = sysUser,
VerificatId = verificatId
};
await WriteTokenToCache(loginPolicy, logingEvent).ConfigureAwait(false);//写入verificat到cache
await UpdateUser(logingEvent).ConfigureAwait(false);
if (sysUser.Account == RoleConst.SuperAdmin)
{
var modules = (await _sysResourceService.GetAllAsync().ConfigureAwait(false)).Where(a => a.Category == ResourceCategoryEnum.Module).OrderBy(a => a.SortCode);//获取模块列表
sysUser.ModuleList = modules.ToList();//模块列表赋值给用户
}
//返回结果
return new LoginOutput
{
VerificatId = verificatId,
Account = sysUser.Account,
Id = sysUser.Id,
ModuleList = sysUser.ModuleList,
AccessToken = accessToken,
RefreshToken = refreshToken
};
@@ -327,9 +320,9 @@ public class AuthService : IAuthService
#endregion ,
using var db = DbContext.Db.GetConnectionScopeWithAttr<SysUser>().CopyNew();
using var db = DbContext.GetDB<SysUser>();
//更新用户登录信息
if (await db.Updateable(sysUser).UpdateColumns(it => new
if (await db.UpdateableT(sysUser).UpdateColumns(it => new
{
it.LastLoginIp,
it.LastLoginTime,
@@ -351,7 +344,7 @@ public class AuthService : IAuthService
//生成verificat信息
var verificatInfo = new VerificatInfo
{
Device = loginEvent.Device,
Device = loginEvent.Device ?? "Unknown",
Expire = loginEvent.Expire,
VerificatTimeout = tokenTimeout,
Id = loginEvent.VerificatId,

View File

@@ -31,7 +31,6 @@ public class OpenApiLoginInput
[Required]
public string Password { get; set; }
/// <summary>
/// 租户Id
///</summary>
@@ -63,5 +62,4 @@ public class LoginInput
/// </summary>
///<example>252885263003720</example>
public long? TenantId { get; set; } = RoleConst.DefaultTenantId;
}

View File

@@ -58,7 +58,7 @@ public class LoginOutput
/// <summary>
/// 模块列表
/// </summary>
public IEnumerable<SysResource> ModuleList { get; set; } = Enumerable.Empty<SysResource>();
public IEnumerable<SysResource>? ModuleList { get; set; } = Enumerable.Empty<SysResource>();
/// <summary>
/// 刷新Token

View File

@@ -15,17 +15,17 @@ public class AppConfig
/// <summary>
/// 登录策略
/// </summary>
public LoginPolicy LoginPolicy { get; set; }
public LoginPolicy? LoginPolicy { get; set; }
/// <summary>
/// 页面策略
/// </summary>
public PagePolicy PagePolicy { get; set; }
public PagePolicy? PagePolicy { get; set; }
/// <summary>
/// 密码策略
/// </summary>
public PasswordPolicy PasswordPolicy { get; set; }
public PasswordPolicy? PasswordPolicy { get; set; }
public WebsitePolicy WebsitePolicy { get; set; }
public WebsitePolicy? WebsitePolicy { get; set; }
}

View File

@@ -15,5 +15,5 @@ public class PagePolicy
/// <summary>
/// 系统默认快捷方式菜单ID列表
/// </summary>
public List<long> Shortcuts { get; set; } = new();
public List<long>? Shortcuts { get; set; } = new();
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
namespace ThingsGateway.Admin.Application;
/// <summary>
@@ -62,7 +60,6 @@ public interface ISysDictService
/// <returns>系统字典项</returns>
Task<SysDict> GetByKeyAsync(string category, string name);
/// <summary>
/// 从缓存/数据库获取自定义配置列表
/// </summary>

View File

@@ -8,11 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using ThingsGateway.FriendlyException;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.Razor;
namespace ThingsGateway.Admin.Application;
@@ -77,7 +74,7 @@ internal sealed class SysDictService : BaseService<SysDict>, ISysDictService
//更新数据
List<SysDict> dicts = new List<SysDict>()
{
new SysDict() { DictType = DictTypeEnum.System, Category = nameof(PagePolicy), Name = nameof(PagePolicy.Shortcuts), Code = input.Shortcuts.ToJsonNetString() },
new SysDict() { DictType = DictTypeEnum.System, Category = nameof(PagePolicy), Name = nameof(PagePolicy.Shortcuts), Code = input.Shortcuts.ToSystemTextJsonString() },
};
var storageable = await db.Storageable(dicts).WhereColumns(it => new { it.DictType, it.Category, it.Name }).ToStorageAsync().ConfigureAwait(false);
@@ -248,7 +245,6 @@ internal sealed class SysDictService : BaseService<SysDict>, ISysDictService
return sysDicts;
}
/// <summary>
/// 从缓存/数据库获取系统配置列表
/// </summary>
@@ -267,7 +263,6 @@ internal sealed class SysDictService : BaseService<SysDict>, ISysDictService
return sysDicts;
}
/// <summary>
/// 表格查询
/// </summary>
@@ -304,7 +299,6 @@ internal sealed class SysDictService : BaseService<SysDict>, ISysDictService
/// <param name="input">配置项</param>
private async Task CheckInput(SysDict input)
{
//设置类型为业务
input.DictType = DictTypeEnum.Define;
@@ -316,7 +310,6 @@ internal sealed class SysDictService : BaseService<SysDict>, ISysDictService
{
throw Oops.Bah(Localizer["DictDup", input.Category, input.Name]);
}
}
/// <summary>
@@ -331,6 +324,5 @@ internal sealed class SysDictService : BaseService<SysDict>, ISysDictService
App.CacheService.Remove($"{CacheConst.Cache_SysDict}{define}{nameof(AppConfig)}");
}
#endregion
}

View File

@@ -10,15 +10,17 @@
using System.Collections.Concurrent;
using ThingsGateway.NewLife.DictionaryExtensions;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 内存推送事件服务
/// </summary>
/// <typeparam name="TEntry"></typeparam>
public class EventService<TEntry> : IEventService<TEntry>
public class EventService<TEntry> : IEventService<TEntry>, IDisposable
{
private ConcurrentDictionary<string, Func<TEntry, Task>> Cache { get; } = new();
private NonBlockingDictionary<string, Func<TEntry, Task>> Cache = new();
public void Dispose()
{

View File

@@ -10,7 +10,6 @@
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using System.Text;
using System.Web;
@@ -26,7 +25,6 @@ internal sealed class FileService : IFileService
IStringLocalizer<FileService> localizer)
{
_localizer = localizer;
}
/// <summary>
/// 获取本地存储文件流
@@ -89,8 +87,4 @@ internal sealed class FileService : IFileService
string[] allowTypeS = allowTypes == null ? ["xlsx"] : allowTypes;//允许上传的文件类型
if (!allowTypeS.Contains(fileSuffix)) throw Oops.Bah(_localizer["FileTypeError", fileSuffix]);
}
#region
#endregion
}

View File

@@ -37,7 +37,6 @@ internal sealed class ImportExportService : IImportExportService
/// <returns></returns>
public async Task<FileStreamResult> ExportAsync<T>(object input, string fileName, bool isDynamicExcelColumn = true) where T : class
{
var path = ImportExportUtil.GetFileDir(ref fileName);
fileName = CommonUtils.GetSingleId() + fileName;
@@ -95,7 +94,5 @@ internal sealed class ImportExportService : IImportExportService
return fileName;
}
#endregion
}

View File

@@ -9,6 +9,7 @@
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
internal sealed class NoticeService : INoticeService
{
private IEventService<AppMessage>? MessageDispatchService { get; set; }
@@ -24,7 +25,6 @@ internal sealed class NoticeService : INoticeService
NavigationMesageDispatchService = navigationMesageDispatchService;
}
/// <inheritdoc/>
public async Task NewMesage(IEnumerable<string>? clientIds, AppMessage message)
{
@@ -51,8 +51,6 @@ internal sealed class NoticeService : INoticeService
}
}
/// <inheritdoc/>
public async Task NavigationMesage(IEnumerable<string>? clientIds, string uri, string message)
{

View File

@@ -32,5 +32,4 @@ public interface ISysHub
/// <param name="message">消息内容</param>
/// <returns>异步操作结果</returns>
Task NewMesage(AppMessage message);
}

View File

@@ -63,5 +63,4 @@ public class SysHub : Hub<ISysHub>
VerificatInfoUtil.UpdateVerificat(userIdentifier, VerificatId, false);//更新cache
return base.OnDisconnectedAsync(exception);
}
}

View File

@@ -11,8 +11,6 @@
using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.SignalR;
using Yitter.IdGenerator;
namespace ThingsGateway.Admin.Application;
/// <summary>
@@ -28,7 +26,7 @@ public class UserIdProvider : IUserIdProvider
if (UserId > 0)
{
return $"{UserId}{SysHub.Separate}{YitIdHelper.NextId()}";//返回用户ID
return $"{UserId}{SysHub.Separate}{CommonUtils.GetSingleId()}";//返回用户ID
}
return connection.ConnectionId;

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using ThingsGateway.Extension.Generic;
namespace ThingsGateway.Admin.Application;

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
namespace ThingsGateway.Admin.Application;
/// <summary>

View File

@@ -8,17 +8,12 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
using System.Data;
namespace ThingsGateway.Admin.Application;
internal sealed class SysOperateLogService : BaseService<SysOperateLog>, ISysOperateLogService
{
#region
/// <summary>
@@ -90,6 +85,4 @@ internal sealed class SysOperateLogService : BaseService<SysOperateLog>, ISysOpe
#endregion
}

View File

@@ -8,10 +8,6 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
namespace ThingsGateway.Admin.Application;
/// <summary>
@@ -59,7 +55,6 @@ public interface ISysOrgService
/// <returns></returns>
Task<HashSet<long>> GetOrgChildIdsAsync(long orgId, bool isContainOneself = true, List<SysOrg> sysOrgList = null);
/// <summary>
/// 根据组织ID获取租户ID
/// </summary>

View File

@@ -8,10 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
using ThingsGateway.Extension.Generic;
using ThingsGateway.FriendlyException;
@@ -126,26 +122,25 @@ internal sealed class SysOrgService : BaseService<SysOrg>, ISysOrgService
{
throw new(result.ErrorMessage, result.ErrorException);
}
}
}
[OperDesc("DeleteOrg")]
public async Task<bool> DeleteOrgAsync(IEnumerable<long> ids)
{
var sysDeleteOrgList = new List<long>();//需要删除的组织ID集合
var sysOrgList = await GetAllAsync().ConfigureAwait(false);//获取所有组织
foreach (var it in ids)
{
var children = SysOrgService.GetSysOrgChildren(sysOrgList, it);//查找下级组织
sysDeleteOrgList.AddRange(children.Select(it => it.Id).ToList());
sysDeleteOrgList.Add(it);
}
//获取所有ID
if (ids.Any())
if (sysDeleteOrgList.Count != 0)
{
using var db = GetDB();
var sysOrgList = await GetAllAsync().ConfigureAwait(false);//获取所有组织
var sysDeleteOrgList = new List<long>();//需要删除的组织ID集合
foreach (var it in ids)
{
var children = SysOrgService.GetSysOrgChildren(sysOrgList, it);//查找下级组织
sysDeleteOrgList.AddRange(children.Select(it => it.Id).ToList());
sysDeleteOrgList.Add(it);
}
//如果组织下有用户则不能删除
if (await db.Queryable<SysUser>().AnyAsync(it => sysDeleteOrgList.Contains(it.OrgId)).ConfigureAwait(false))
{
@@ -169,10 +164,8 @@ internal sealed class SysOrgService : BaseService<SysOrg>, ISysOrgService
{
return false;
}
}
/// <summary>
/// 从缓存/数据库获取系统配置列表
/// </summary>
@@ -247,7 +240,6 @@ internal sealed class SysOrgService : BaseService<SysOrg>, ISysOrgService
return reuslt;
}
/// <inheritdoc />
public async Task<HashSet<long>> GetOrgChildIdsAsync(long orgId, bool isContainOneself = true, List<SysOrg> sysOrgList = null)
{
@@ -277,8 +269,6 @@ internal sealed class SysOrgService : BaseService<SysOrg>, ISysOrgService
return childList;
}
/// <inheritdoc />
public async Task<List<SysOrg>> GetTenantListAsync()
{
@@ -384,7 +374,6 @@ internal sealed class SysOrgService : BaseService<SysOrg>, ISysOrgService
);
}
/// <summary>
/// 获取组织所有下级
/// </summary>
@@ -409,7 +398,6 @@ internal sealed class SysOrgService : BaseService<SysOrg>, ISysOrgService
return new List<SysOrg>();
}
/// <summary>
/// 重新生成组织实体
/// </summary>
@@ -429,7 +417,6 @@ internal sealed class SysOrgService : BaseService<SysOrg>, ISysOrgService
/// </summary>
private async Task CheckInput(SysOrg input)
{
if (!(await SysUserService.GetUserByIdAsync(UserManager.UserId).ConfigureAwait(false)).IsGlobal)
{
if (input.ParentId == 0)
@@ -471,8 +458,6 @@ internal sealed class SysOrgService : BaseService<SysOrg>, ISysOrgService
}
}
/// <summary>
/// 刷新缓存
/// </summary>
@@ -487,7 +472,6 @@ internal sealed class SysOrgService : BaseService<SysOrg>, ISysOrgService
_dispatchService.Dispatch(null);
}
/// <summary>
/// 获取全称
/// </summary>
@@ -510,7 +494,6 @@ internal sealed class SysOrgService : BaseService<SysOrg>, ISysOrgService
return parentIdList;
}
/// <inheritdoc />
private static List<SysOrg> GetOrgParents(List<SysOrg> allOrgList, long orgId, bool includeSelf = true)
{

View File

@@ -10,9 +10,6 @@
namespace ThingsGateway.Admin.Application;
public class PositionSelectorInput : UserSelectorInput
{
}

View File

@@ -10,7 +10,6 @@
namespace ThingsGateway.Admin.Application;
public class PositionTreeOutput
{
/// <summary>
@@ -34,7 +33,6 @@ public class PositionTreeOutput
public List<PositionTreeOutput> Children { get; set; } = new List<PositionTreeOutput>();
}
public class PositionSelectorOutput
{
/// <summary>

View File

@@ -8,10 +8,6 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
namespace ThingsGateway.Admin.Application;
/// <summary>
@@ -66,8 +62,6 @@ public interface ISysPositionService
/// <returns></returns>
Task<List<PositionSelectorOutput>> SelectorAsync(PositionSelectorInput input);
#endregion
}

View File

@@ -8,10 +8,6 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
using ThingsGateway.FriendlyException;
namespace ThingsGateway.Admin.Application;
@@ -79,7 +75,7 @@ public class SysPositionService : BaseService<SysPosition>, ISysPositionService
}
var dels = (await GetAllAsync().ConfigureAwait(false)).Where(a => ids.Contains(a.Id));
await SysUserService.CheckApiDataScopeAsync(dels.Select(a => a.OrgId).ToList(), dels.Select(a => a.CreateUserId).ToList()).ConfigureAwait(false);
await SysUserService.CheckApiDataScopeAsync(dels.Select(a => a.OrgId), dels.Select(a => a.CreateUserId)).ConfigureAwait(false);
//删除职位
var result = await base.DeleteAsync(ids).ConfigureAwait(false);
if (result)
@@ -90,7 +86,6 @@ public class SysPositionService : BaseService<SysPosition>, ISysPositionService
{
return false;
}
}
/// <summary>
@@ -284,6 +279,4 @@ public class SysPositionService : BaseService<SysPosition>, ISysPositionService
}
}

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