Compare commits

..

534 Commits

Author SHA1 Message Date
Kimdiego2098
2c197ed2b2 2.1.0.7 2023-09-05 09:06:39 +08:00
Kimdiego2098
d8fc6665b3 fix:error!the stream dispose 2023-09-05 09:04:34 +08:00
Kimdiego2098
c671a79822 Revert "fix:error!the stearm dispose"
This reverts commit a6d99fe227.
2023-09-05 09:03:49 +08:00
Kimdiego2098
9d93ce4c41 feat:mqttBroker part 2023-09-05 08:58:49 +08:00
Kimdiego2098
a6d99fe227 fix:error!the stearm dispose 2023-09-05 08:57:42 +08:00
Diego2098
923b8bca31 feat:mqttBroker part 2023-09-04 22:34:31 +08:00
Diego2098
e2c30d1c88 fix:uploadDeivce复制多个,ID重复问题 2023-09-04 22:11:50 +08:00
Diego2098
b6d9f2a04e 刷新后选择行清空 2023-09-04 22:09:26 +08:00
Diego2098
57306ea664 fix:采集设备线程初始化时更新活跃时间 2023-09-04 20:13:49 +08:00
Diego2098
cd7f3fd02f fix:特殊情况下无法获取程序集文件修改日期,所以去除 页脚-编译时间显示 2023-09-04 19:49:17 +08:00
Kimdiego2098
0482e077a8 fix:sqlsugar json支持类型改为bigstring 2023-09-04 17:09:38 +08:00
Kimdiego2098
5f986a45ca 更新文档 2023-09-04 15:03:03 +08:00
Kimdiego2098
ca7b49c0d5 恢复Sqlite默认数据库 2023-09-02 19:17:32 +08:00
Kimdiego2098
52dd555e6c fix: page取消注入瞬时服务,改为App.GetService 2023-09-02 19:13:57 +08:00
Kimdiego2098
579b1a59f9 feat:mqttBroker part 2023-09-02 13:55:21 +08:00
Kimdiego2098
5299c5c4be 更新文档 2023-08-31 09:17:59 +08:00
Kimdiego2098
f7756bccef 更新 变量管理-上传设备筛选功能 2023-08-31 08:58:30 +08:00
Kimdiego2098
a6b874d160 2.1.0.6 2023-08-30 17:19:18 +08:00
Kimdiego2098
3e5fb3ddcf add windows service create/delete bat 2023-08-30 17:18:35 +08:00
Kimdiego2098
5e6bcb12d3 update statusPage; 2023-08-30 14:12:50 +08:00
Kimdiego2098
14303f1429 默认检测重连频率为10分钟 2023-08-29 17:56:59 +08:00
Kimdiego2098
96711ba022 2.1.0.5 2023-08-29 17:44:28 +08:00
Kimdiego2098
cbfc0fdbdc 添加报文日志入库选项 2023-08-29 17:42:56 +08:00
Kimdiego2098
6e81886c0e 添加 页脚 编译时间显示 2023-08-29 17:01:46 +08:00
Kimdiego2098
2d976bc132 调整导入excel错误提示 2023-08-29 16:45:54 +08:00
Kimdiego2098
57f6a476af update opcdaclient null error 2023-08-29 15:24:29 +08:00
Kimdiego2098
8491ed296e update GetBoolValue 2023-08-29 12:40:21 +08:00
Kimdiego2098
cd1288afdc 调整种子文件,增加Pro版本种子文件录入 2023-08-29 09:42:54 +08:00
Kimdiego2098
ec6c830cb0 更新2.1.0.4 2023-08-28 19:31:00 +08:00
Kimdiego2098
2f86ccc4bf 修复串口基类 缓存包 失效错误;完善modbusrtu长度校验 2023-08-28 19:25:22 +08:00
Kimdiego2098
8ca445aec0 更新touchsocket版本,更新2.1.0.3 2023-08-28 18:12:18 +08:00
Kimdiego2098
1e1f27c8a5 修复浏览大量节点空间时,BrowseNext方法参数releaseContinuationPoints改为false 2023-08-28 17:44:02 +08:00
Kimdiego2098
2b84bde367 修复调试界面-opcuaclient浏览节点展开逻辑错误 2023-08-28 10:51:35 +08:00
Kimdiego2098
b52e58551d 更新小版本2.1.0.2 2023-08-27 17:17:37 +08:00
Kimdiego2098
9aceed00bf 更新dlt645地址说明 2023-08-27 17:16:59 +08:00
Kimdiego2098
58814f7f74 添加日志查询 时间区间条件 2023-08-27 16:58:45 +08:00
Kimdiego2098
6a70ef9f31 2.1.0.1 2023-08-27 16:31:03 +08:00
Kimdiego2098
82cc4ca500 dlt645添加数据标识校验 2023-08-27 16:30:32 +08:00
Kimdiego2098
4567fa04ed 更新dlt645,添加地址/控制码的校验规则 2023-08-27 16:15:51 +08:00
Kimdiego2098
8b98b5d818 更新touchsocket 2023-08-27 15:59:57 +08:00
Kimdiego2098
176d0351af 更新 Opc.Ua Version="1.4.372.56" 2023-08-27 14:33:24 +08:00
Kimdiego2098
d63dc3384b opcda/ua浏览地址空间 初始只加载首层节点 2023-08-27 14:14:47 +08:00
Kimdiego2098
1ccd704e30 opcuaclient浏览地址空间 初始只加载首层节点 2023-08-27 13:06:03 +08:00
Diego2098
f5d23dbe79 update MqttNetLogger 2023-08-26 21:38:14 +08:00
Diego2098
75bfe53ac3 update MqttNetLogger 2023-08-26 20:40:23 +08:00
Diego2098
3308f916dd 添加在线/离线方法参数 2023-08-26 17:18:47 +08:00
Diego2098
e7140279ca 更新opcuaclient 2023-08-26 15:38:22 +08:00
Diego2098
1034719f5e 更新opcuaclient,添加checkDomain属性,去除多余代码,更新SelectEndpoint方法 2023-08-26 15:33:03 +08:00
Kimdiego2098
2c00043a7f 更新文档 2023-08-25 19:49:02 +08:00
Kimdiego2098
65c695d9ce 更新版本2.1.0 2023-08-25 19:45:28 +08:00
Kimdiego2098
57253fe46a 更新解决方案 2023-08-25 19:38:47 +08:00
Kimdiego2098
4e5c443440 更新DLT645文档 2023-08-25 19:33:33 +08:00
Kimdiego2098
0b3b73d8ec 添加DLT645_2007协议插件 2023-08-25 19:33:08 +08:00
Kimdiego2098
921eabc134 调整Test位置 2023-08-25 19:32:27 +08:00
Kimdiego2098
0faa428751 调整非主从协议的状态判断策略;OPCUA去除主动连接 2023-08-25 17:23:00 +08:00
Kimdiego2098
f71a2fdd63 crc校验优化 2023-08-22 17:01:10 +08:00
Kimdiego2098
4eb9ed8aba OPCUAClient添加是否使用SourceTime的选项 2023-08-22 16:15:34 +08:00
Kimdiego2098
d7b549abb8 更新文档 2023-08-22 12:21:45 +08:00
Kimdiego2098
95d723c578 2.0.9.3 2023-08-22 11:31:36 +08:00
Kimdiego2098
2fcd853e86 去除mqtt throw an _MqttConnectingFailedException_.报错信息 2023-08-22 11:26:22 +08:00
Kimdiego2098
07eef7c812 更新批量写入方法,注意rpc入口参数变化,mqtt等RPC接口参数变化 2023-08-22 11:23:03 +08:00
Kimdiego2098
b01e0757fa 修正部分代码格式 2023-08-21 19:59:07 +08:00
Kimdiego2098
32844a20c6 2.0.9.2 2023-08-19 12:08:49 +08:00
Kimdiego2098
5b6532c601 xml 2023-08-19 11:06:05 +08:00
Kimdiego2098
2c5b4b4027 修复TcpClient重复释放导致锁异常 2023-08-19 11:05:29 +08:00
Kimdiego2098
72d7ecf195 mas1.0.2默认中文 2023-08-18 18:36:30 +08:00
Kimdiego2098
2cfa6b4306 更新masa1.0.2 2023-08-18 13:59:17 +08:00
Kimdiego2098
6f6ffde0ab 2.0.9.1 2023-08-18 13:33:45 +08:00
Kimdiego2098
1694739a16 修改锁为EasyLock 2023-08-18 12:07:01 +08:00
Kimdiego2098
95d1e8bfca 代码文件编码统一utf-8 2023-08-18 09:57:03 +08:00
Kimdiego2098
60dec08e3c 更换DEMO Include为PackageReference 2023-08-17 15:30:35 +08:00
Kimdiego2098
a99d71be93 底层驱动添加netstandard输出 2023-08-17 15:24:07 +08:00
Kimdiego2098
f1331b6a0c 2.0.8 2023-08-17 10:58:48 +08:00
Kimdiego2098
10d66b642b 调整readme,方便copy账密 2023-08-17 10:28:47 +08:00
Kimdiego2098
cd2310e4a8 优化变量地址输入分割方法;s7读写字符串添加默认编码 2023-08-17 09:50:10 +08:00
Kimdiego2098
1b399cf6b0 优化s7读写字符串,更改特殊方法为读写共用 2023-08-16 17:20:20 +08:00
Kimdiego2098
877445bc0a 修复写入返回结果时的解析,0XFF为成功 2023-08-16 15:56:25 +08:00
Kimdiego2098
9a5b345bde 修复s7字符串打包读取 2023-08-16 15:47:37 +08:00
Kimdiego2098
fc9e8ea7b3 修复s7字符串读取 2023-08-16 15:35:15 +08:00
Kimdiego2098
32be6fcfc1 主动释放锁 2023-08-16 14:56:47 +08:00
Kimdiego2098
49847236c2 OPCUA死区不再固定传入 2023-08-16 14:21:19 +08:00
Kimdiego2098
d8424443e6 fix: 优化JsonUtils中的获取type语句顺序 2023-08-16 08:59:18 +08:00
Kimdiego2098
f3b571ec3f OPC系列增加导出excel/导入系统双选项 2023-08-15 17:38:40 +08:00
Kimdiego2098
99318bb5d7 OPCUA采集设备写入字符串,不再需要传入双引号 2023-08-15 14:15:39 +08:00
Kimdiego2098
1aa154c9aa 历史数据/报警 线程初始化时不再阻塞 2023-08-15 12:12:00 +08:00
Kimdiego2098
c65d8a445b 更改ItemGroup Condition条件 2023-08-14 17:36:59 +08:00
Kimdiego2098
80f4f85570 传播token;
更新admin解决方案
2023-08-14 16:43:30 +08:00
Kimdiego2098
5beee43a6b 优化适配器解析代码 2023-08-14 14:21:00 +08:00
Kimdiego2098
8d6ae203a0 2.0.6 2023-08-13 16:57:12 +08:00
Kimdiego2098
4353479a5c OPCUACClient订阅初始化添加trycatch 2023-08-13 16:29:59 +08:00
Kimdiego2098
34d7687f9e Merge branch 'master' of https://gitee.com/dotnetchina/ThingsGateway 2023-08-13 15:53:33 +08:00
Kimdiego2098
b1dc3cf4af 更新opcuaclient,初始读取server类型改为动态读取 2023-08-13 15:50:47 +08:00
Diego2098
6a58b95933 更新nuget包 2023-08-12 12:09:50 +08:00
Kimdiego2098
d3badfd02b 修复批量令牌强退失效 2023-08-11 15:15:50 +08:00
Kimdiego2098
0098be057b 中间变量导入时添加标识 2023-08-11 14:15:22 +08:00
Kimdiego2098
6f972aa515 2.0.5 2023-08-10 18:23:40 +08:00
Kimdiego2098
7407ba6313 去除不需要的模板 2023-08-10 16:28:52 +08:00
Kimdiego2098
1c79de207b 修改详情模板 2023-08-10 11:23:18 +08:00
Kimdiego2098
257c79db92 添加自定义详情模板 2023-08-10 10:35:19 +08:00
Kimdiego2098
9d1934a308 底层修改:如果连接端口断开,停止等待返回 2023-08-10 10:35:01 +08:00
Kimdiego2098
d70f959902 上传DTO添加 单位 参数 2023-08-10 09:09:09 +08:00
Kimdiego2098
e4d810222f 调整调试页面 2023-08-09 23:45:53 +08:00
Kimdiego2098
bc1af4ae07 添加单文件发布配置 2023-08-09 15:12:55 +08:00
Kimdiego2098
6e688ef43f 去除页面多余引号 2023-08-09 13:45:27 +08:00
Kimdiego2098
f0fe1b23dc 更新2.0.4 2023-08-09 13:00:10 +08:00
Kimdiego2098
aaf2006401 更新opcua写入 2023-08-09 12:43:05 +08:00
Kimdiego2098
b821e26935 更新opcua调试页面 2023-08-09 12:01:23 +08:00
Kimdiego2098
7ae4287157 更新opcua 2023-08-09 11:47:04 +08:00
Kimdiego2098
c6fcc38a65 修复中间变量导入错误 2023-08-09 09:00:49 +08:00
Kimdiego2098
ab2d5c8853 修正错误注释 2023-08-08 18:09:23 +08:00
Kimdiego2098
5e557ff0bc opcua添加初始化连接错误重试 2023-08-08 18:04:24 +08:00
Kimdiego2098
918ca449a1 更新readme 2023-08-08 16:14:06 +08:00
Kimdiego2098
8e73368008 优化大量设备线程重启的耗时 2023-08-08 14:02:00 +08:00
Kimdiego2098
f3c1faf672 2.0.3 2023-08-08 12:59:14 +08:00
Kimdiego2098
d6df04dd6a 底层部分方法更改 2023-08-08 12:31:42 +08:00
Kimdiego2098
b1b9e51ab6 报警/历史值查询时区转换 2023-08-08 09:05:15 +08:00
Kimdiego2098
e49d4770ac 时间最小最大值时不再转换时区 2023-08-08 08:39:20 +08:00
Kimdiego2098
8fa1075511 Merge branch 'master' of https://gitee.com/dotnetchina/ThingsGateway 2023-08-08 08:28:37 +08:00
Kimdiego2098
9a70169b94 默认不启用单用户登录 2023-08-08 08:28:27 +08:00
Diego2098
fefb928237 更新文档 2023-08-07 22:31:51 +08:00
Diego2098
ad7e700d0d 去除DateTimeOffset类型 2023-08-07 22:30:38 +08:00
Kimdiego2098
1699c69147 更新文档 2023-08-07 17:38:53 +08:00
Kimdiego2098
1695f7cece 更新readme 2023-08-07 17:34:32 +08:00
Kimdiego2098
052c27f907 更新文件 2023-08-07 17:24:28 +08:00
Kimdiego2098
dc46c32b30 更新文件 2023-08-07 17:16:53 +08:00
Kimdiego2098
fa63349bb2 更新文件 2023-08-07 16:56:45 +08:00
Kimdiego2098
ffe26448a6 更新文件 2023-08-07 16:33:36 +08:00
Kimdiego2098
4af51e8a84 更新文件 2023-08-07 16:30:23 +08:00
Kimdiego2098
1e453cf5a5 更新文件 2023-08-07 15:46:30 +08:00
Kimdiego2098
591282b87d 更新文件 2023-08-07 15:36:49 +08:00
Kimdiego2098
e87528d520 去除多余解决方案配置 2023-08-07 15:31:22 +08:00
Kimdiego2098
d79eb0411d 添加发布脚本 2023-08-07 15:23:53 +08:00
Kimdiego2098
ac1e0a4cf7 更新readme 2023-08-07 15:18:42 +08:00
Kimdiego2098
9525eab130 更新readme 2023-08-07 15:13:21 +08:00
Kimdiego2098
89b317496c 更新文档 2023-08-07 15:10:42 +08:00
Kimdiego2098
13be91e78b 2.0.0 2023-08-07 15:09:53 +08:00
Kimdiego2098
f68c1437f3 调试更新Blazor代码时不再跳转登录界面 2023-07-25 22:00:27 +08:00
Kimdiego2098
4c64c969bb 取消不必要的错误日志 2023-07-25 20:50:33 +08:00
Kimdiego2098
b4bf3b5138 变量值更新错误提示; 2023-07-25 20:48:17 +08:00
Kimdiego2098
083bc4b400 优化大批量excel变量表导入效率 2023-07-25 18:26:48 +08:00
Kimdiego2098
e8683c5bcc 删除不必要延时 2023-07-25 16:52:56 +08:00
Kimdiego2098
80e0d1de91 发布1.7.6 2023-07-25 14:22:11 +08:00
Kimdiego2098
dbe841037e 优化导入excel效率 2023-07-25 14:21:17 +08:00
Kimdiego2098
bdd537c33c 添加上传插件获取采集设备属性值的快捷方法 2023-07-23 13:44:37 +08:00
Kimdiego2098
c0c3846094 Merge branch 'master' of https://gitee.com/dotnetchina/ThingsGateway 2023-07-23 09:59:11 +08:00
Kimdiego2098
9e8710e7d2 线程完成时不再需要全局数据移除,有可能会导致偶发全局数据丢失 2023-07-23 09:58:43 +08:00
Kimdiego2098
475553fdf6 线程完成时不要需要全局数据移除,有可能会导致偶发全局数据丢失 2023-07-23 09:57:03 +08:00
Kimdiego2098
9d570f5b45 更新icon包版本 2023-07-23 00:03:46 +08:00
Kimdiego2098
af7fafd34f 更新icon包版本 2023-07-22 23:59:03 +08:00
Kimdiego2098
d43130f4fc 修改插件日志为自定义日志 2023-07-22 23:34:09 +08:00
Kimdiego2098
7500194620 更新文档 2023-07-22 20:48:29 +08:00
Kimdiego2098
eb27c29144 发布1.7.4 2023-07-22 20:47:26 +08:00
Kimdiego2098
43260b3e24 修正错误名称 2023-07-22 20:32:34 +08:00
Kimdiego2098
f80713f0aa 添加上传插件最后错误原因 2023-07-22 20:10:45 +08:00
Kimdiego2098
0c4bdc7ad1 修复上传设备暂停按钮失效 2023-07-22 19:56:56 +08:00
Kimdiego2098
811cff7bd0 OPCUAServer 数组维度不再写any;rpc写入添加延时 2023-07-22 17:02:44 +08:00
Kimdiego2098
30269aa75c 更改假死检测间隔5分钟 2023-07-22 11:05:56 +08:00
Kimdiego2098
e345ef7083 添加OPCUAServer线程间隔配置项,优化最后一次错误提示 2023-07-22 11:03:56 +08:00
Kimdiego2098
f559c9b8f7 支持OPCUAServer数组类型,添加缓存文件存在判断 2023-07-21 21:28:39 +08:00
Kimdiego2098
f4af0916b2 去除OPCUA写入数组维度校验 2023-07-20 17:37:49 +08:00
Kimdiego2098
f15f14f28d 更新文档 2023-07-20 12:48:13 +08:00
Kimdiego2098
834f44f58d kafka尝试自动加载c库 2023-07-20 12:40:59 +08:00
Kimdiego2098
b36f45dcf4 修复中间变量Rpc写入 2023-07-20 10:55:37 +08:00
Kimdiego2098
11ba21c9a8 ModbusServer添加自定义循环间隔,修复中间变量写入 2023-07-20 10:55:17 +08:00
Kimdiego2098
b045557ce1 更新文档 2023-07-19 18:02:31 +08:00
Kimdiego2098
0dd251a3f6 更新文档 2023-07-19 15:43:28 +08:00
Kimdiego2098
793acb1725 更改左侧菜单为手风琴效果 2023-07-19 15:00:40 +08:00
Kimdiego2098
921243e8bd 更新文档 2023-07-19 14:34:27 +08:00
Kimdiego2098
bd9d7a90d9 修改OPCUAClient心跳频率默认3s 2023-07-18 14:49:27 +08:00
Kimdiego2098
cc444a4cea 更改OPCUAClient心跳频率为30000 2023-07-18 14:15:02 +08:00
Kimdiego2098
38ca1fa168 更新1.7.3版本 2023-07-18 12:16:33 +08:00
Kimdiego2098
7a552b87ec 更新masa 稳定版以及其他包 2023-07-18 12:11:37 +08:00
Kimdiego2098
36923d3190 修复添加订阅时,值死区过滤逻辑 2023-07-18 11:13:21 +08:00
Kimdiego2098
a9d3017123 修复异步锁上下文切换导致OPCUA 心跳事件出错 2023-07-18 09:58:01 +08:00
Kimdiego2098
313acd4976 修复OPCUAClient调试界面重复输出订阅值 2023-07-18 08:51:27 +08:00
Kimdiego2098
a4c91bb268 修改OPCUAClient的心跳频率配置项 2023-07-18 08:48:18 +08:00
Kimdiego2098
f9b566984b 添加重启锁 2023-07-17 21:40:47 +08:00
2248356998 qq.com
8dd261854d Merge branch 'master' of https://gitee.com/diego2098/thingsgateway-docs 2023-07-17 20:59:53 +08:00
2248356998 qq.com
7351e62d87 更新opcua心跳状态日志 2023-07-17 20:59:32 +08:00
Diego2098
0593ae720b 更新文档 2023-07-16 20:46:16 +08:00
Diego2098
a0a7b08e08 更新授权名单 2023-07-16 20:31:56 +08:00
2248356998 qq.com
9a3bc6b8b3 更新文档 2023-07-16 18:27:22 +08:00
2248356998 qq.com
5acae17f71 更新文档 2023-07-16 18:12:55 +08:00
2248356998 qq.com
f1e5b76ef2 更新文档 2023-07-16 18:07:31 +08:00
2248356998 qq.com
53c628fde9 更新文档 2023-07-16 17:49:33 +08:00
2248356998 qq.com
baca0a70c0 更新文档 2023-07-16 17:48:22 +08:00
2248356998 qq.com
3e8d0af404 更新文档 2023-07-16 17:41:52 +08:00
2248356998 qq.com
cf9a91d9d5 更新文档 2023-07-16 17:35:04 +08:00
2248356998 qq.com
02b9e282c6 更新文档地址 2023-07-16 17:32:49 +08:00
2248356998 qq.com
9ce87f235f 迁移文档 2023-07-16 17:28:26 +08:00
2248356998 qq.com
e329bea1b2 冗余设备删除后会导致后台出错 2023-07-16 11:36:45 +08:00
2248356998 qq.com
8086e7b54d 更新readme 2023-07-16 10:28:43 +08:00
2248356998 qq.com
f7a875606e 删除无用属性 2023-07-16 09:40:00 +08:00
2248356998 qq.com
196eaf85f4 修复1.7.0版本修改导致的mqttrpc映射错误 2023-07-15 22:56:28 +08:00
2248356998 qq.com
876a55668e 增加 上传插件的列表分割大小,因为某些情况下传输字节太大会导致失败 2023-07-15 22:42:34 +08:00
2248356998 qq.com
05bd21bdd5 导入提示的当前行显示未初始 2023-07-15 22:35:12 +08:00
2248356998 qq.com
fb51a08cc6 格式化整理 2023-07-15 22:32:54 +08:00
2248356998 qq.com
dd83d7f4d3 插件报文截取前200字符,防止页面渲染过多 2023-07-15 20:04:14 +08:00
2248356998 qq.com
842a56f7ce kafka插件增加超时选项 2023-07-15 17:27:35 +08:00
2248356998 qq.com
9246a6e797 历史服务修复变量在线状态显示错误 2023-07-15 15:46:39 +08:00
2248356998 qq.com
8ad693f717 发布1.7.2版本 2023-07-15 11:10:29 +08:00
2248356998 qq.com
f4c2ee7cc4 优化OPCUA错误提示 2023-07-14 17:33:11 +08:00
2248356998 qq.com
6043441faa OPCUA在取消订阅时应该走读取方法 2023-07-14 17:22:19 +08:00
2248356998 qq.com
4a065c3710 优化导入excel提示 2023-07-14 16:23:42 +08:00
2248356998 qq.com
0ef800bdd7 优化导入excel提示 2023-07-14 16:23:35 +08:00
2248356998 qq.com
56eaa1910d kafka 插件释放时取消事件注册 2023-07-14 14:35:40 +08:00
2248356998 qq.com
201788e286 更改属性说明 2023-07-14 12:23:46 +08:00
2248356998 qq.com
506e0f144f 添加kafka插件null传播 2023-07-14 10:41:04 +08:00
2248356998 qq.com
72f68bfdd9 opcda JValue转object 2023-07-14 10:14:05 +08:00
2248356998 qq.com
2f9869b11d opcua读取值JValue转为object 2023-07-14 10:04:05 +08:00
2248356998 qq.com
8ffcf6498c RabbitMQ,IotSharp添加离线缓存 2023-07-13 19:54:12 +08:00
2248356998 qq.com
d224ae1923 Variable Value数据类型改为object 2023-07-13 18:18:53 +08:00
2248356998 qq.com
fed2063a19 中间变量页种子ID重复 2023-07-13 17:47:22 +08:00
2248356998 qq.com
db2810cdd7 复制设备没有及时刷新缓存 2023-07-13 17:38:45 +08:00
2248356998 qq.com
4f1a6781ef 发布1.7.1 2023-07-13 11:19:17 +08:00
2248356998 qq.com
beffa5d5a4 代码格式化 2023-07-13 11:18:36 +08:00
2248356998 qq.com
7a20f1de07 OPCDA增加数组支持,增加写入动态类型支持 2023-07-13 11:16:31 +08:00
2248356998 qq.com
cd25cf726b 优化OPCUA数组类型转换 2023-07-13 09:02:36 +08:00
2248356998 qq.com
d6b1bc3842 修复blazor界面null错误 2023-07-12 22:12:11 +08:00
2248356998 qq.com
a4385fb9bb 更新ReadMe 2023-07-12 21:33:05 +08:00
2248356998 qq.com
7045f2b8ea 删除重复文件 2023-07-12 21:30:41 +08:00
2248356998 qq.com
07ca1a4de8 更新nuget包 2023-07-12 21:21:50 +08:00
2248356998 qq.com
24f289e692 V1.7.0发布
1、增加采集通道冗余
2、优化多个界面
3、导出导入功能优化
4、增加中间变量
5、OPCUAClient支持动态类型
6、离线缓存多处覆盖,包含上传插件/历史报警/时序库
7、增加通用调试界面,增加s7调试
8、其他优化
2023-07-12 21:16:38 +08:00
2248356998 qq.com
01bcdaae2d 修复AppDataTable清空MForm模型导致筛选列清空的问题 2023-07-10 13:18:14 +08:00
2248356998 qq.com
55890008d1 修复写入s7协议 bit值 偏移错误(以byte数据块为准) 2023-07-08 17:01:44 +08:00
2248356998 qq.com
5ab9b01879 修复读取s7协议 bit值 偏移错误(以byte数据块为准) 2023-07-08 11:44:57 +08:00
2248356998 qq.com
e4abb333b3 默认添加kafka插件 种子数据 2023-07-07 17:13:00 +08:00
2248356998 qq.com
09f476c745 修复布尔量解析时反转字节导致结果值不符的错误 2023-07-06 14:05:09 +08:00
2248356998 qq.com
8806e68dce null传播 2023-07-03 14:13:00 +08:00
2248356998 qq.com
2ef1e25cd8 添加x86架构 2023-07-03 10:48:52 +08:00
2248356998 qq.com
10e7f202aa 补充插件实例内容 2023-07-02 19:50:09 +08:00
2248356998 qq.com
ccd7000c09 blazor组件初始化并行执行,导致sugar单例DB出现线程偶发错误,暂增加copyNew()解决 2023-06-29 16:42:19 +08:00
2248356998 qq.com
8ee7b798cf blazor组件初始化并行执行,导致sugar单例DB出现线程偶发错误,暂增加copyNew()解决 2023-06-29 16:36:03 +08:00
2248356998 qq.com
7733cf5bf0 sqlsugar偶发线程故障,添加copyNew方法 2023-06-28 18:54:45 +08:00
2248356998 qq.com
a05ce86dd7 登录跳转uri优化 2023-06-28 15:47:35 +08:00
2248356998 qq.com
91f51c32e8 更新开源说明 2023-06-28 10:48:33 +08:00
2248356998 qq.com
f910202bba update openapiUser datatableUI 2023-06-26 19:51:02 +08:00
2248356998 qq.com
6d77194a8f css脚本结果不采用 System.Text.Json.JsonSerializer 2023-06-26 19:44:17 +08:00
2248356998 qq.com
9deb89c15f OPCUAClient修复当变量为string类型时的数值过滤失败导致添加订阅失效 2023-06-26 19:43:00 +08:00
2248356998 qq.com
4b62a092b4 停用采集设备时,变量获取运行态出错 2023-06-26 16:43:48 +08:00
2248356998 qq.com
81c8f626f9 登录后跳转原url 2023-06-26 14:46:24 +08:00
2248356998 qq.com
3e846c42fb cookie授权验证失败时返回登录界面 2023-06-26 14:36:41 +08:00
Diego2098
63ad7fd766 更新nuget类库 2023-06-25 18:59:09 +08:00
2248356998 qq.com
9ff1e9aa34 添加报警事件None枚举 2023-06-20 13:10:58 +08:00
2248356998 qq.com
8d162b6f3d 修复cpu核心等于1时报错,修正登录错误提示 2023-06-20 09:09:08 +08:00
Diego2098
9844d10bef 修复modbusServer 初始化错误 2023-06-17 18:14:13 +08:00
2248356998 qq.com
b908fa8489 sugar添加取消令箭 2023-06-15 17:38:36 +08:00
2248356998 qq.com
15a10643a7 增加变量运行态 CollectVariableRuntime 所在设备属性 2023-06-15 16:50:08 +08:00
2248356998 qq.com
299617aca1 parallel.foreach无序体验不好,退回为foreach 2023-06-14 11:04:09 +08:00
2248356998 qq.com
45647d697a update 1.6.1 version 2023-06-13 22:39:47 +08:00
2248356998 qq.com
48f5105d38 update nuget package 2023-06-13 22:39:25 +08:00
2248356998 qq.com
fe1c741d68 update driver messagesui 2023-06-13 22:26:19 +08:00
2248356998 qq.com
fa42cc1f00 并行关闭线程 2023-06-12 14:41:08 +08:00
2248356998 qq.com
42cf5e7a81 添加mqtt/kafka上传内容显示 2023-06-12 14:40:53 +08:00
2248356998 qq.com
47905e1aa1 优化大量变量excel上传的验证过程 2023-06-12 11:35:27 +08:00
2248356998 qq.com
9a8e907df3 更新文档站点地址 2023-06-11 18:37:50 +08:00
2248356998 qq.com
106fe85582 删除docs 2023-06-11 18:22:15 +08:00
2248356998 qq.com
4b3571bd57 更新1.6.0版本 2023-06-11 17:58:57 +08:00
2248356998 qq.com
96b537401a 缓存最大默认2000 2023-06-11 17:56:34 +08:00
2248356998 qq.com
721c9eb057 添加离线缓存大小限制配置 2023-06-11 17:55:03 +08:00
2248356998 qq.com
51701bf6d6 上传插件线程等待时间改为10ms 2023-06-11 17:47:16 +08:00
2248356998 qq.com
dbde68bd56 导入变量优化 2023-06-11 17:46:23 +08:00
2248356998 qq.com
ad2c9f585a 添加mqttClient离线缓存 2023-06-11 17:45:46 +08:00
2248356998 qq.com
562093c468 添加kafka离线缓存 2023-06-11 17:45:27 +08:00
Diego2098
b0295584a3 !6 部分配置会导致SqlServer自动建库失败
Merge pull request !6 from samisgod/master
2023-06-10 06:55:34 +00:00
samisgod
208c54de98 fix db init for SqlServer 2023-06-10 14:51:29 +08:00
2248356998 qq.com
63e2d941a1 增加pwa 2023-06-09 17:53:41 +08:00
2248356998 qq.com
3956838e9c 修复停用验证码时登录失败显示null报错 2023-06-09 17:09:17 +08:00
2248356998 qq.com
abeee58bb0 统一文件编码 2023-06-09 15:04:54 +08:00
2248356998 qq.com
d5b1b49722 update solution folder 2023-06-09 14:30:53 +08:00
2248356998 qq.com
564ed03ff8 upload deviceStatusPage 2023-06-09 10:32:11 +08:00
2248356998 qq.com
70db4c76b4 update deviceStatusPage 2023-06-09 09:49:03 +08:00
2248356998 qq.com
d059f7975b remove dotNET China declaration 2023-06-09 09:10:30 +08:00
2248356998 qq.com
4e74e6dc2d add dotNET China declaration 2023-06-09 09:02:17 +08:00
Diego2098
b6deb96658 重启线程时增加运行状态界面空传播防止报错 2023-06-08 21:35:07 +08:00
2248356998 qq.com
3839e966be add upload plugin code description 2023-06-08 17:47:53 +08:00
2248356998 qq.com
3dd035849c 迁移导入变量功能到驱动调试内 2023-06-08 15:11:58 +08:00
2248356998 qq.com
3d6532b5d6 plugin unload test,but failed 2023-06-08 10:52:39 +08:00
2248356998 qq.com
bf7c175ee7 更改默认api文档为Knife4j 2023-06-07 20:34:31 +08:00
2248356998 qq.com
f84af35ed6 缓存键增加Type-TypeHandle句柄 2023-06-07 18:42:58 +08:00
2248356998 qq.com
99063b3eb1 修改OPCUA证书路径,增加默认接收不收信任证书与其选项;
OPCDA整理;
2023-06-07 18:03:57 +08:00
2248356998 qq.com
3bec18f28d 重启线程WebApi修改 2023-06-07 11:32:18 +08:00
2248356998 qq.com
15de7a7894 添加注释提示 2023-06-07 11:28:11 +08:00
2248356998 qq.com
e20e04e677 GC策略更改,大量变量实例手动清空以快速内存释放 2023-06-07 11:19:24 +08:00
2248356998 qq.com
5fc6ae2835 上传1.5.1 2023-06-06 19:37:04 +08:00
2248356998 qq.com
7d281b8c96 Merge branch 'master' of https://gitee.com/diego2098/ThingsGateway 2023-06-05 08:33:50 +08:00
Diego2098
4880b801a7 S7-TCP连接修复死锁 2023-06-02 22:20:21 +08:00
2248356998 qq.com
74e354456a 增加KINGVIEW 配置 2023-05-29 01:32:37 +08:00
2248356998 qq.com
af2e03aa36 超管用户名称不可更改 2023-05-26 00:14:05 +08:00
2248356998 qq.com
d8fa660ab6 初步添加OPCUAClient调试界面;更新依赖 2023-05-25 23:41:11 +08:00
2248356998 qq.com
1a62d48297 OPCUA安全策略添加全部选项 2023-05-25 00:59:21 +08:00
2248356998 qq.com
7ba01be13d OPCUA安全策略添加全部选项 2023-05-25 00:58:47 +08:00
2248356998 qq.com
1a83d64db7 添加OPCDAClient调试页面 2023-05-25 00:11:00 +08:00
2248356998 qq.com
5b53014c40 修改程序根目录为文件所在目录 2023-05-24 14:37:45 +08:00
2248356998 qq.com
83685340af 默认添加服务守护支持 2023-05-24 14:32:21 +08:00
2248356998 qq.com
31e0cc4dec ModbusServer添加自定义数据类型;修复发布时静态文件路径错误 2023-05-24 12:31:20 +08:00
2248356998 qq.com
56b87fc1f5 Add copyright notices 2023-05-23 23:54:28 +08:00
2248356998 qq.com
6b956a2dd7 update TGTcpClient 2023-05-23 22:42:48 +08:00
2248356998 qq.com
1937623d7d 添加Modbus系列插件调试页面;添加Modbus组包解析缓存超时时间; 2023-05-23 20:50:44 +08:00
2248356998 qq.com
3b60b10945 update HardwareInfoService 2023-05-23 12:04:04 +08:00
2248356998 qq.com
7173acd350 nuget更新 2023-05-23 11:59:28 +08:00
2248356998 qq.com
6310d87338 硬件界面添加限值防报错 2023-05-23 11:46:19 +08:00
2248356998 qq.com
49a1ed7c18 初步添加插件驱动调试页面 2023-05-22 18:50:30 +08:00
2248356998 qq.com
d426e280d9 初步添加插件驱动调试页面 2023-05-22 18:41:09 +08:00
2248356998 qq.com
6154fb29f1 Razor文件格式化清理 2023-05-22 18:40:50 +08:00
2248356998 qq.com
97d48ef9d6 删除调试代码... 2023-05-22 17:09:21 +08:00
2248356998 qq.com
88992625c4 更改变量读取间隔限制最低为10ms 2023-05-22 15:17:22 +08:00
2248356998 qq.com
bc6eb44218 明确System.Management版本 2023-05-22 14:51:02 +08:00
2248356998 qq.com
cf9ccd799d 硬件信息获取添加异常拦截 2023-05-22 14:33:30 +08:00
2248356998 qq.com
ffa0e4e771 update adapter 2023-05-22 12:42:31 +08:00
2248356998 qq.com
60fa9c196c 代码清理 2023-05-21 22:39:33 +08:00
2248356998 qq.com
df860d22fb 优化opcua质量戳提示;添加数据转换基础方法;TCP等待返回时默认断开连接立即返回 2023-05-21 10:51:56 +08:00
2248356998 qq.com
cb46ff326c modbus 组包优化 2023-05-20 21:41:16 +08:00
2248356998 qq.com
f277a853ef 类型更换 2023-05-20 17:13:14 +08:00
2248356998 qq.com
9ae34f67c3 可指定OPCUA节点数据类型 2023-05-20 15:40:17 +08:00
2248356998 qq.com
c9223218cc 更新文档 2023-05-20 13:52:23 +08:00
2248356998 qq.com
c0dd645aba 添加自定义OPCUAServer数据类型 2023-05-20 12:34:06 +08:00
2248356998 qq.com
2e948eb5b6 导入规则优化,主动抛出名称重复错误 2023-05-19 22:55:53 +08:00
2248356998 qq.com
c3276889cf 更改插件时开启刷新设备属性 2023-05-19 22:26:37 +08:00
2248356998 qq.com
a76ca8282d 判断OPCUAServer状态时增加tryCatch 2023-05-19 21:27:20 +08:00
2248356998 qq.com
8ce6b8362f 判断OPCUAServer状态时增加tryCatch 2023-05-19 21:24:56 +08:00
2248356998 qq.com
842fb12f05 优化tcp拆包组包 2023-05-19 20:28:51 +08:00
2248356998 qq.com
d63e1511af 快捷方式null错误 2023-05-19 16:33:57 +08:00
2248356998 qq.com
278783b8e0 更新依赖包 2023-05-19 16:23:21 +08:00
2248356998 qq.com
d24e3c922d UDP通讯优化 2023-05-19 16:21:42 +08:00
2248356998 qq.com
1d02cd2283 添加链路锁 2023-05-19 14:27:47 +08:00
2248356998 qq.com
8edeb82a87 分包数量显示错误 2023-05-19 10:13:48 +08:00
2248356998 qq.com
146e9279de Modbus读写锁更新 2023-05-19 10:05:54 +08:00
2248356998 qq.com
47105f50a9 Modbus读写锁更新 2023-05-19 09:53:56 +08:00
2248356998 qq.com
16c9c80f37 删除sqlsugar旧版本代码,会导致sqlite不兼容 2023-05-19 09:15:32 +08:00
2248356998 qq.com
8e7e4bc95a 适配mysql;修复写入表达式转换;优化web页面写入体验 2023-05-18 23:38:40 +08:00
2248356998 qq.com
0aa3d2f930 数据库初始化修复 2023-05-18 22:24:27 +08:00
2248356998 qq.com
ce77755a1e 数据库连接自动释放 2023-05-18 21:06:06 +08:00
2248356998 qq.com
0f31f20c87 编辑页面添加 变量 允许远程写入选项 2023-05-18 21:03:44 +08:00
2248356998 qq.com
ee6da2aaa5 修复枚举类型在mysql中类型出错的问题 2023-05-18 20:58:14 +08:00
Diego2098
a35f087cd9 修改文件大小限制 2023-05-17 22:29:59 +08:00
Diego2098
6e029b44dd 更新设备读写锁 2023-05-17 22:05:31 +08:00
2248356998 qq.com
973c0cff34 touchSocket修复UDP在windows下重置连接的问题;更新nuget 2023-05-17 16:54:33 +08:00
2248356998 qq.com
2027eea6ac 代码格式清理 2023-05-17 16:49:25 +08:00
2248356998 qq.com
2f43692f33 修复CancellationTokenSource未释放导致Linux下偶发内存问题 2023-05-17 16:44:52 +08:00
2248356998 qq.com
6d24992f88 OPCUAServer节点数据类型增加在读取表达式转换后的判断 2023-05-16 09:05:58 +08:00
Diego2098
b4388a58d6 ModbusClient添加帧前时间 2023-05-15 22:45:55 +08:00
2248356998 qq.com
158aa05fac 修复using作用域导致获取的服务可能被释放的问题 2023-05-15 19:21:35 +08:00
2248356998 qq.com
f2731bf55e nuget更新 2023-05-15 18:55:27 +08:00
2248356998 qq.com
7304e99fce mqttClient同步间隔上传的实体类 2023-05-12 18:22:06 +08:00
2248356998 qq.com
02700b83eb update mqttClient 2023-05-12 16:40:55 +08:00
2248356998 qq.com
676b25acf9 MqttClient增加间隔上传选项;更改线程循环间隔说明定义 2023-05-12 16:31:57 +08:00
Diego2098
556359ea2d update readme 2023-05-10 21:18:43 +08:00
Diego2098
b72923e0f5 增加api控制采集启停等方法 2023-05-10 21:17:34 +08:00
Diego2098
115ac9f75e 提交kafka插件 2023-05-10 21:17:09 +08:00
2248356998 qq.com
32e36f6708 update readme 2023-05-10 17:49:52 +08:00
2248356998 qq.com
d949b7a4f9 共享链路写入变量修复;Nuget更新 2023-05-10 16:33:59 +08:00
2248356998 qq.com
eae1171ff5 更新文档 2023-05-09 17:51:55 +08:00
2248356998 qq.com
76a1b75a51 更新文档 2023-05-09 17:47:22 +08:00
2248356998 qq.com
8882c0daea OPCUAClient/Server 修改证书有效期为100年 2023-05-09 14:37:18 +08:00
2248356998 qq.com
07ebc16d59 添加控制台logo 2023-05-08 18:10:07 +08:00
2248356998 qq.com
0ceb109964 Code Cleanup 2023-05-08 18:03:32 +08:00
2248356998 qq.com
118b0d0038 add GetSciptValue 2023-05-08 17:45:13 +08:00
2248356998 qq.com
5e87067792 modbus rtu 粘包优化 2023-05-08 15:59:35 +08:00
2248356998 qq.com
c946a252e8 modbus rtu报文粘包优化;共享链路切换延时;上传插件添加报文界面 2023-05-08 15:57:33 +08:00
2248356998 qq.com
f9ad2ba1dd plc read with CancellationToken 2023-05-08 13:33:01 +08:00
2248356998 qq.com
0d0ecd33bd 粘包优化 2023-05-08 10:55:16 +08:00
2248356998 qq.com
e4b98fd05b Rpc条件bug修复 2023-05-07 18:06:27 +08:00
2248356998 qq.com
95a5933303 OPCUAServer修复匿名登录 2023-05-07 18:06:09 +08:00
2248356998 qq.com
da3b55fa64 更新文档 2023-05-07 16:56:41 +08:00
2248356998 qq.com
fbbabfb90e 属性顺序调整 2023-05-05 09:46:11 +08:00
2248356998 qq.com
f13da6830d 更新文档 2023-05-04 22:58:36 +08:00
2248356998 qq.com
f560a8e2f8 update CollectDeviceThread 2023-05-04 10:58:45 +08:00
2248356998 qq.com
56f1139c2f 更新文档 2023-05-02 22:11:55 +08:00
2248356998 qq.com
773bdfc1e2 更新1.5.0 2023-05-02 22:06:09 +08:00
2248356998 qq.com
f449666628 1、共享链路支持;
2、设备报文查看;
3、采集线程重构;
2023-05-02 21:58:11 +08:00
2248356998 qq.com
3f282de0ab mqtt重连锁优化 2023-04-27 11:19:32 +08:00
2248356998 qq.com
440dd8d22f 添加常用转换 2023-04-24 17:54:00 +08:00
2248356998 qq.com
dcff9de2f7 masa更新 2023-04-24 11:16:28 +08:00
2248356998 qq.com
a192866543 更新包 2023-04-24 09:26:47 +08:00
Diego2098
10081416de 报警后台服务去除多余接口
Signed-off-by: Diego2098 <2248356998@qq.com>
2023-04-19 00:48:13 +00:00
2248356998 qq.com
e2bed618f9 添加docker文件 2023-04-17 17:39:12 +08:00
2248356998 qq.com
03ab1f3823 单文件发布 2023-04-17 15:13:40 +08:00
2248356998 qq.com
ac8aeb63d9 update SqlSugarConfig 2023-04-17 11:58:41 +08:00
2248356998 qq.com
2e16d822fa 删除其他信息 2023-04-17 11:05:38 +08:00
2248356998 qq.com
e407d873fa 验证码更新修复 2023-04-17 09:05:44 +08:00
2248356998 qq.com
fd712a1dbe 1、字段null约束修改
2、ModbusServer绑定端口优化
2023-04-16 15:02:09 +08:00
2248356998 qq.com
e9028b40ce 更新readme 2023-04-16 13:34:29 +08:00
2248356998 qq.com
c9da3dee7c 更新1.4.0
注意Excel导入已不适用以前版本
1、去除动态更新插件
2、改用MiniExcel,支持动态导入,excel配置更简单
3、优化多处界面与后台逻辑,部分方法规范更名
4、修复外网地址获取错误等
2023-04-15 20:48:56 +08:00
2248356998 qq.com
c8c224e202 修复报警文本逻辑 2023-04-11 13:40:13 +08:00
2248356998 qq.com
f34559daaf 1,修复控制台报错(echarts.js问题,已删除)2,多处Dispose修正 2023-04-07 08:48:34 +08:00
2248356998 qq.com
9fefbf4c27 过滤 2023-04-06 14:46:24 +08:00
2248356998 qq.com
1af9fd73ea 历史数据库选择为sqlite时查询转换日期错误 2023-04-05 18:04:33 +08:00
2248356998 qq.com
75ef394eff 启用开发环境web详细日志,调整App.Razor位置,添加网页ico 2023-04-05 17:14:26 +08:00
2248356998 qq.com
ec6cc2c63e update console/file datetime format 2023-04-05 15:49:00 +08:00
2248356998 qq.com
06bc2e192b 更新readme 2023-04-04 18:41:56 +08:00
2248356998 qq.com
78701ec7c1 😀版本1.3.1 2023-04-04 17:55:52 +08:00
2248356998 qq.com
c925fab7e4 网关软件时间统一UTC 2023-04-04 17:55:15 +08:00
2248356998 qq.com
42fd72c164 update iotSharpClient 2023-04-04 11:07:05 +08:00
2248356998 qq.com
7fd160e1a2 😀 更新1.3.0 2023-04-04 10:15:34 +08:00
2248356998 qq.com
97a0d940eb update iotSharpClient 2023-04-04 09:25:16 +08:00
2248356998 qq.com
efaa099d81 update iotSharpClient 2023-04-04 09:23:22 +08:00
2248356998 qq.com
47864a804b IosSharpClient Rpc优化 2023-04-04 09:22:22 +08:00
2248356998 qq.com
91136c0e43 IotSharp Rpc方法完善 2023-04-04 09:19:48 +08:00
2248356998 qq.com
28c3b1bd61 添加写入多个变量的api方法 2023-04-04 09:18:58 +08:00
2248356998 qq.com
551352bc40 update IotSharpClient 2023-04-03 20:16:38 +08:00
2248356998 qq.com
e73c24c925 更新种子 2023-04-03 19:34:12 +08:00
2248356998 qq.com
7ec4c286cc 添加IotSharp插件 2023-04-03 19:30:52 +08:00
2248356998 qq.com
6705e2ec4b 分页显示令牌 2023-04-03 15:29:35 +08:00
2248356998 qq.com
6f0373063b 后台启动时Furion RootServices NULL值 2023-04-03 15:19:59 +08:00
2248356998 qq.com
f64eef60b5 修复不存在采集设备时,初始化报警/历史服务bug 2023-04-03 14:12:56 +08:00
2248356998 qq.com
89546bf86b 弹窗消息在SignalR订阅方法中需InvokeAsync 2023-04-03 13:35:14 +08:00
2248356998 qq.com
793678feca 修复规范化结果包装2次导致登录返回结果不正确的问题 2023-04-03 12:45:19 +08:00
2248356998 qq.com
923cc3019a 更新演示地址 2023-04-03 10:47:49 +08:00
2248356998 qq.com
10eb98a5f6 readme 2023-04-02 18:12:18 +08:00
2248356998 qq.com
bd9e89d8dd readme 2023-04-02 18:08:56 +08:00
2248356998 qq.com
1926b4ce73 更新readme 2023-04-02 18:05:54 +08:00
2248356998 qq.com
4ef3062d74 更新readme 2023-04-02 18:05:28 +08:00
2248356998 qq.com
abb6e0f60f 更新包 2023-04-02 17:10:32 +08:00
2248356998 qq.com
f204d8d84e 添加注释 2023-04-02 16:59:46 +08:00
2248356998 qq.com
fa301656f1 调整依赖,添加关系图 2023-04-01 17:28:35 +08:00
2248356998 qq.com
7e1221028f 调整依赖,更新版本1.2.1 2023-04-01 15:45:02 +08:00
2248356998 qq.com
41308cb2dd 整理 2023-04-01 13:57:57 +08:00
2248356998 qq.com
130600521c 😀 OPCUAServer支持历史查询数据 2023-03-31 18:32:55 +08:00
2248356998 qq.com
cd57548a48 添加ThingsGateway.Foundation注释 2023-03-31 16:25:33 +08:00
2248356998 qq.com
efacc99f76 硬件信息获取添加延时 2023-03-30 20:51:08 +08:00
2248356998 qq.com
f0d236e172 脚本显示优化 2023-03-30 19:51:38 +08:00
2248356998 qq.com
a8118bd8c6 更新文档 2023-03-30 19:39:35 +08:00
2248356998 qq.com
0e58f2ef53 mqtt/mq上传 添加上传实体自定义脚本 2023-03-30 19:07:18 +08:00
2248356998 qq.com
f4b22b3a0c 表达式整理 2023-03-30 16:23:03 +08:00
2248356998 qq.com
df5bd281c7 更新版本 2023-03-30 14:12:39 +08:00
2248356998 qq.com
a3f23837ce 更新文档 2023-03-30 14:11:16 +08:00
2248356998 qq.com
612d989b97 readme 2023-03-30 14:06:03 +08:00
2248356998 qq.com
42c01ee9a2 更新readme,nuget 2023-03-30 14:05:00 +08:00
2248356998 qq.com
14074db591 删除多余代码 2023-03-30 13:53:57 +08:00
2248356998 qq.com
43dfdd7942 opcua 写入添加用户名日志 2023-03-30 13:32:32 +08:00
2248356998 qq.com
f397b97ccf 去除ua数据类型验证 2023-03-30 13:20:26 +08:00
2248356998 qq.com
95f8716144 更新readme 2023-03-30 13:13:44 +08:00
2248356998 qq.com
17ba472b2e 类命名错误更改 2023-03-30 13:11:46 +08:00
2248356998 qq.com
42d82571ab 发布文件添加 2023-03-30 13:11:43 +08:00
2248356998 qq.com
9119a28141 添加OPCUAServer插件,修复个别bug 2023-03-30 13:10:31 +08:00
Diego2098
a32263d838 1、OPCUAClient修复客户端证书未自动生成
2、修复异步方法错误使用
2023-03-30 00:15:13 +08:00
2248356998 qq.com
208ae2bb88 添加部分代码注释 2023-03-29 16:22:01 +08:00
2248356998 qq.com
4d85462a85 驱动支持主机名称 2023-03-29 11:07:21 +08:00
2248356998 qq.com
f601aa9ca0 修复初始化失败导致的一系列问题 2023-03-29 10:53:42 +08:00
2248356998 qq.com
8aee3ad455 上传插件间隔时间修正 2023-03-28 16:40:00 +08:00
2248356998 qq.com
6a2a1e9561 历史变量enable失效 2023-03-28 16:38:29 +08:00
2248356998 qq.com
5f8786c9dc 历史报表时间格式改为yyyy-MM-dd HH:mm:ss ffffff 2023-03-28 16:31:53 +08:00
2248356998 qq.com
73f1d3eead 采集设备状态判断错误 2023-03-28 16:21:30 +08:00
2248356998 qq.com
2bf21bb3c3 采集设备状态判断错误 2023-03-28 15:55:01 +08:00
2248356998 qq.com
f80f0dbb11 修复历史保存值 频繁时出现值相同的问题 2023-03-28 15:14:30 +08:00
2248356998 qq.com
37518c70c4 opc 活动时间修正 2023-03-28 14:15:14 +08:00
2248356998 qq.com
e5951b5bef 添加写入采集时间的接口,而不是固定DateTime.Now 2023-03-28 14:03:11 +08:00
2248356998 qq.com
ab320bd90b 更新tcpclient 2023-03-28 13:52:33 +08:00
2248356998 qq.com
7bd36b5371 修复linux 分隔符错误问题 2023-03-28 11:48:16 +08:00
2248356998 qq.com
b882b0f2bc 更改插件文件不存在时的日志信息 2023-03-28 10:21:39 +08:00
2248356998 qq.com
38d7ae73cc 更改项目引用结构 2023-03-28 10:21:23 +08:00
2248356998 qq.com
4527c6ee5d swagger文档 2023-03-27 19:01:04 +08:00
Iot边缘设备
85829e70c1 !5 添加部署文档
* 添加部署文档
2023-03-27 10:22:30 +00:00
2248356998 qq.com
256c08d82a 属性/字段缓存获取只包含public 2023-03-27 14:49:09 +08:00
2248356998 qq.com
c2ce03c047 更新masa-1.0.0-preview.10 2023-03-27 14:27:15 +08:00
2248356998 qq.com
f2af19e198 优化变量上传属性页 2023-03-27 12:00:10 +08:00
2248356998 qq.com
930b7c092d 采集设备活动时间应正常刷新 2023-03-27 10:51:54 +08:00
2248356998 qq.com
00757c69c6 添加设备复制功能 2023-03-27 10:42:02 +08:00
2248356998 qq.com
55f267d0fc update code collation 2023-03-27 08:49:10 +08:00
2248356998 qq.com
6b96aff6e8 update code collation 2023-03-26 19:48:08 +08:00
2248356998 qq.com
32b773a8fa 更新nuget包 2023-03-26 18:52:16 +08:00
2248356998 qq.com
03089adad6 发行1.10版本 2023-03-26 18:10:12 +08:00
2248356998 qq.com
4a1fe746ab 实时数据界面优化 2023-03-26 17:39:05 +08:00
2248356998 qq.com
aa52c05d2c 1、采用异步AutoResetEvent,修复多个设备下采集太慢的问题
2、弃用IntelligentConcurrentQueue(touchsocket集成类)
2023-03-26 17:32:29 +08:00
2248356998 qq.com
26407a43e7 1、优化设备状态页,减少signalr刷新所需包大小
2、日志前置等级添加
2023-03-24 18:32:10 +08:00
2248356998 qq.com
a02934bf19 修复导出表格时,null值报错 2023-03-23 22:09:53 +08:00
2248356998 qq.com
09c65fba09 导入错误时,忽略大于10行的错误信息显示 2023-03-23 20:01:19 +08:00
2248356998 qq.com
4305c727d0 更新readme 2023-03-22 15:29:35 +08:00
2248356998 qq.com
188339897f 优化log等级、json配置注解 2023-03-22 15:26:42 +08:00
2248356998 qq.com
4ecff9a707 OPCUA写入测试 2023-03-22 14:06:32 +08:00
2248356998 qq.com
355aed49c6 设备运行状态页面改为扩展 2023-03-22 13:46:23 +08:00
2248356998 qq.com
4717b6b0f0 更新文档 2023-03-22 10:44:44 +08:00
Diego2098
45ebe9048d rbmq插件优化 2023-03-21 23:38:00 +08:00
Diego2098
b2170c49a3 分包失败提示 2023-03-21 23:28:30 +08:00
Diego2098
dc2f4d6115 设备组列宽调整,补充上传设备组 2023-03-21 23:10:59 +08:00
Diego2098
1eb132440f 1、更新DataTypeEnum
2、OPCUA写入时需实际数据类型
2023-03-21 22:47:10 +08:00
Diego2098
a464bbc37a 更新Tests 2023-03-21 22:46:12 +08:00
2248356998 qq.com
ed995697c2 表格无数据时,修改默认宽度 2023-03-21 18:17:18 +08:00
2248356998 qq.com
163cd84c7b S7nuget版本 2023-03-21 15:44:50 +08:00
2248356998 qq.com
293d7cc292 masa nuget版本更改 2023-03-21 14:31:36 +08:00
2248356998 qq.com
5de1b4e74c GC策略修改 2023-03-21 14:07:17 +08:00
2248356998 qq.com
7b474975da 更新文档 2023-03-21 14:03:49 +08:00
2248356998 qq.com
beab51516b 更新文档 2023-03-21 13:56:14 +08:00
2248356998 qq.com
fe8685a50c 修改s7属性注释 2023-03-21 13:51:05 +08:00
2248356998 qq.com
f9af5d0885 更新readme 2023-03-21 13:49:10 +08:00
2248356998 qq.com
e8136a9720 更新S7协议插件 2023-03-21 13:45:58 +08:00
2248356998 qq.com
531e5d4556 修改S7特殊方法 2023-03-21 13:41:32 +08:00
2248356998 qq.com
e66255963a 修改s7 2023-03-21 13:38:43 +08:00
2248356998 qq.com
246aac8ee4 添加西门子S7协议插件 2023-03-21 13:37:17 +08:00
2248356998 qq.com
23cfeff685 版本恢复 2023-03-21 11:45:18 +08:00
2248356998 qq.com
a5e7e0d126 更新nuget,暂缓链路复用功能 2023-03-20 13:34:59 +08:00
2248356998 qq.com
5bebc30ba0 默认不开启转储 2023-03-20 09:22:11 +08:00
2248356998 qq.com
0e7057f5b9 更新opcda文档 2023-03-19 23:18:24 +08:00
2248356998 qq.com
7c6c365ba4 格式清理 2023-03-19 23:12:58 +08:00
2248356998 qq.com
424c9bb0c5 修复null值报错 2023-03-19 23:02:08 +08:00
2248356998 qq.com
9d0f26594c 添加设备组,变量按设备组别搜索 2023-03-19 22:56:02 +08:00
2248356998 qq.com
99c17de079 更新readme,opcda核心库提示 2023-03-19 20:40:54 +08:00
2248356998 qq.com
b1e3dd0af6 尝试修复中文在mac上乱码 2023-03-19 17:29:01 +08:00
2248356998 qq.com
261cb89530 修正中文在mac上乱码 2023-03-19 16:45:14 +08:00
2248356998 qq.com
ff6773ba37 删除未知文件 2023-03-19 16:35:24 +08:00
2248356998 qq.com
bdfbbfcbbd Merge branch 'master' of https://gitee.com/diego2098/ThingsGateway 2023-03-19 16:34:26 +08:00
2248356998 qq.com
0c4cd56758 添加设备组功能 2023-03-19 16:34:15 +08:00
士心
4a36658321 !4 修复MacOS环境下,数据库报错的问题
* 修复macos环境下,数据库报错
2023-03-19 08:28:54 +00:00
2248356998 qq.com
7aae938685 准备更新设备组/变量组功能 2023-03-19 11:49:06 +08:00
2248356998 qq.com
3723401e7a 更新ReadMe 2023-03-18 17:56:18 +08:00
2248356998 qq.com
70631366a9 1、最后校验失败时应该提示日志
2、暂停时设备状态修正
2023-03-18 17:49:08 +08:00
2248356998 qq.com
0e40bbda3e 更新文档 2023-03-18 16:47:29 +08:00
2248356998 qq.com
e9aa475398 ByteBlock实际长度 2023-03-18 16:45:39 +08:00
2248356998 qq.com
8d2a811184 修正串口描述类ToString 2023-03-18 16:32:15 +08:00
2248356998 qq.com
dd7f5b6700 更新文档 2023-03-18 16:23:23 +08:00
2248356998 qq.com
a4f6277737 更新nuget 2023-03-18 16:16:27 +08:00
2248356998 qq.com
c2bfaacbb7 更新readme 2023-03-18 16:15:14 +08:00
2248356998 qq.com
a17cbfa2d4 添加ModbusRtu种子数据 2023-03-18 16:14:37 +08:00
2248356998 qq.com
fb9a101555 添加ModbusRtu插件 2023-03-18 16:14:25 +08:00
2248356998 qq.com
e319cf0200 添加串口基础类 2023-03-18 16:14:08 +08:00
2248356998 qq.com
0a8395ef6a 更新文档 2023-03-17 18:19:14 +08:00
2248356998 qq.com
38df5e01be 更新文档 2023-03-17 17:52:04 +08:00
2248356998 qq.com
ebd891a868 种子数据更改 2023-03-17 17:51:58 +08:00
2248356998 qq.com
4ab2395cbe 采集检测间隔修改 2023-03-17 17:40:34 +08:00
2248356998 qq.com
5f1f989fc9 上传插件添加循环间隔属性 2023-03-17 17:30:06 +08:00
2248356998 qq.com
44b709eee3 添加rbmq插件种子数据 2023-03-17 16:30:02 +08:00
2248356998 qq.com
d0d7726597 rbmq插件文件夹更名 2023-03-17 16:16:23 +08:00
2248356998 qq.com
054c342aeb 优化内存queue,修复mqtt插件可能超出字节限制的情况 2023-03-17 16:04:24 +08:00
2248356998 qq.com
c79c33baf7 添加rbmq插件 2023-03-17 16:03:44 +08:00
2248356998 qq.com
23b00e35b2 设备禁用时,变量绑定的设备对应选项会显示禁用(灰色) 2023-03-17 11:29:14 +08:00
2248356998 qq.com
fe51079266 更新readme 2023-03-16 17:33:25 +08:00
2248356998 qq.com
0791b0bbee 选择插件时自动更新属性 2023-03-16 17:33:17 +08:00
2248356998 qq.com
dbf04c8eeb 更新赞助名单 2023-03-16 11:35:58 +08:00
2248356998 qq.com
6204256df8 限流服务默认不开启 2023-03-16 09:47:20 +08:00
2248356998 qq.com
93cc8c2327 增加简易定时看板 2023-03-15 16:01:54 +08:00
2248356998 qq.com
68a2e5bbbc 更新历史配置说明 2023-03-15 09:11:33 +08:00
Diego2098
72792153f2 !3 update handbook/docs/05、网关配置/5.5、其他配置.mdx.
Merge pull request !3 from zhubanghao/N/A
2023-03-14 13:39:16 +00:00
zhubanghao
88b6ef1897 update handbook/docs/05、网关配置/5.5、其他配置.mdx.
Signed-off-by: zhubanghao <58813184@qq.com>
2023-03-14 13:05:33 +00:00
1533 changed files with 136390 additions and 41823 deletions

63
.gitattributes vendored Normal file
View File

@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

5
.gitignore vendored
View File

@@ -360,4 +360,7 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
/framework/*Pro*

View File

@@ -87,7 +87,7 @@
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Cachetribution. You may reproduce and distribute copies of the
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:

111
README.md
View File

@@ -1,95 +1,38 @@
# ThingsGateway

## 介绍
## Introduction
**NetCore** 跨平台边缘采集网关(工业设备采集)

A cross-platform, high-performance edge data collection gateway based on net8, capable of handling millions of data points per.

**ThingsGateway** 存储库同时提供 [**设备采集驱动**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22)
## Documentation

[Documentation](https://thingsgateway.cn/).

[NuGet](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22)

### Plugin List

#### Data Collection Plugins
**ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 [**ThingsGateway.Admin**](https://gitee.com/dotnetchina/ThingsGateway/blob/master/framework/ThingsGateway.Admin.sln)
| Plugin Name | Remarks |
| ----------- | ------------------------------------------------------------- |
| Modbus | Supports Rtu/Tcp message formats, with Serial/Tcp/Udp links |
| SiemensS7 | Siemens PLC S7 series |
| Dlt6452007 | Supports Serial/Tcp/Udp links |
| OpcDaMaster | Compiled for 64-bit |
| OpcUaMaster | Supports certificate login, object extension, Json read/write |
## 文档
#### Business Plugins
[ThingsGateway](https://diego2098.gitee.io/thingsgateway-docs/) 文档。
## 协议
[ThingsGateway](https://gitee.com/diego2098/ThingsGateway) 采用 [Apache-2.0](https://gitee.com/diego2098/ThingsGateway/blob/master/LICENSE.zh) 开源协议。
## 演示
[ThingsGateway演示地址](http://120.24.62.140:5000/)
账户 : **superAdmin**
密码 : **111111**
## 赞助
[ThingsGateway赞助途径](https://diego2098.gitee.io/thingsgateway-docs/docs/donate)
## 社区
QQ群605534569
| Plugin Name | Remarks |
| ---------------- | ------------------------------------------------------------------------------------------------- |
| ModbusSlave | Supports Rtu/Tcp message formats, with Serial/Tcp/Udp links, supports Rpc reverse writing |
| OpcUaServer | OpcUa server, supports Rpc reverse writing |
| MqttClient | Mqtt client, supports Rpc reverse writing, script-customizable upload content |
| MqttServer | Mqtt server, supports WebSocket, supports Rpc reverse writing, script-customizable upload content |
| KafkaProducer | Script-customizable upload content |
| RabbitMQProducer | Script-customizable upload content |
| SqlDB | Relational database storage, supports historical storage and real-time data updates |
| SqlHistoryAlarm | Alarm historical data relational database storage |
| TDengineDB | Time-series database storage |
| QuestDB | Time-series database storage |

## License

[Apache-2.0](https://gitee.com/diego2098/ThingsGateway/blob/master/LICENSE)

## Demo

[Demo](http://47.119.161.158:5000/)

Account: **SuperAdmin**

Password: **111111**

**In the upper-right corner, switch to the IoT Gateway module in the personal popup box**
## Docker
```shell
docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway
docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64
```

## Sponsorship

[Sponsorship Approach](https://thingsgateway.cn/docs/1000)

## Community

QQ Group: 605534569 [Jump](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569)

## Pro Plugins

[Plugin List](https://thingsgateway.cn/docs/1001)

View File

@@ -1,75 +0,0 @@
# ThingsGateway
## 介绍
基于net8的跨平台高性能边缘采集网关单机采集数据点位可达百万
## 文档
[文档](https://thingsgateway.cn/)
[NuGet](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22)
### 插件列表
#### 采集插件
| 插件名称 | 备注 |
| ----------- | ------------------------------------- |
| Modbus | Rtu/Tcp报文格式支持串口/Tcp/Udp链路 |
| SiemensS7 | 西门子PLC S7系列 |
| Dlt6452007 | 支持串口/Tcp/Udp链路 |
| OpcDaMaster | 64位编译 |
| OpcUaMaster | 支持证书登录扩展对象Json读写 |
#### 业务插件
| 插件名称 | 备注 |
| ---------------- | ---------------------------------------------------------- |
| ModbusSlave | Rtu/Tcp报文格式支持串口/Tcp/Udp链路支持Rpc反写 |
| OpcUaServer | OpcUa服务端支持Rpc反写 |
| MqttClient | Mqtt客户端支持Rpc反写脚本自定义上传内容 |
| MqttServer | Mqtt服务端支持WebSocket支持Rpc反写脚本自定义上传内容 |
| KafkaProducer | 脚本自定义上传内容 |
| RabbitMQProducer | 脚本自定义上传内容 |
| SqlDB | 关系数据库存储,支持历史存储和实时数据更新 |
| SqlHistoryAlarm | 报警历史数据关系数据库存储 |
| TDengineDB | 时序数据库存储 |
| QuestDB | 时序数据库存储 |
## 协议
[Apache-2.0](https://gitee.com/diego2098/ThingsGateway/blob/master/LICENSE)
## 演示
[ThingsGateway演示地址](http://47.119.161.158:5000/)
账户 : **SuperAdmin**
密码 : **111111**
**右上角个人弹出框中,切换到物联网关模块**
## Docker
```shell
docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway
docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64
```
## 赞助
[赞助途径](https://thingsgateway.cn/docs/1000)
## 社区
QQ群605534569 [跳转](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569)
## Pro插件
[插件列表](https://thingsgateway.cn/docs/1001)

149
framework/.editorconfig Normal file
View File

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

View File

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

View File

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

View File

@@ -0,0 +1,76 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DynamicApiController;
using Microsoft.AspNetCore.Mvc;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 文件下载
/// </summary>
[ApiDescriptionSettings(CateGoryConst.ThingsGatewayAdmin, Order = 200)]
[Route("file")]
[LoggingMonitor]
public class FileController : IDynamicApiController
{
private readonly IFileService _fileService;
private readonly IOperateLogService _operateLogService;
private readonly IVisitLogService _visitLogService;
/// <summary>
/// <inheritdoc cref="FileController"/>
/// </summary>
public FileController(
IFileService fileService,
IOperateLogService operateLogService,
IVisitLogService visitLogService
)
{
_fileService = fileService;
_operateLogService = operateLogService;
_visitLogService = visitLogService;
}
/// <summary>
/// 下载操作日志
/// </summary>
/// <returns></returns>
[HttpGet("operateLog")]
public async Task<IActionResult> DownloadOperateLogAsync([FromQuery] OperateLogInput input)
{
var memoryStream = await _operateLogService.ExportFileAsync(input);
memoryStream.Seek(0, SeekOrigin.Begin);
var data = new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = $"operateLog{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx"
};
return data;
}
/// <summary>
/// 下载访问日志
/// </summary>
/// <returns></returns>
[HttpGet("visitLog")]
public async Task<IActionResult> DownloadVisitLogAsync([FromQuery] VisitLogInput input)
{
var memoryStream = await _visitLogService.ExportFileAsync(input);
memoryStream.Seek(0, SeekOrigin.Begin);
var data = new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = $"operateLog{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx"
};
return data;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,43 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 操作事件说明特性
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class OperDescAttribute : Attribute
{
/// <summary>
/// 操作记录标识
/// </summary>
/// <param name="description"></param>
/// <param name="catcategory"></param>
public OperDescAttribute(string description, string catcategory = LogConst.LOG_OPERATE)
{
Description = description;
Catcategory = catcategory;
}
/// <summary>
/// 分类
/// </summary>
public string Catcategory { get; }
/// <summary>
/// 说明
/// </summary>
public string Description { get; }
/// <summary>
/// 记录参数默认位true
/// </summary>
public bool IsRecordPar { get; set; } = true;
}

View File

@@ -0,0 +1,225 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.Reflection;
using Furion.Reflection.Extensions;
using System.Reflection;
using System.Text;
using ThingsGateway.Admin.Core;
using ThingsGateway.Admin.Core.JsonExtensions;
using UAParser;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// AOP处理操作日志
/// </summary>
public class OperDispatchProxy : AspectDispatchProxy, IDispatchProxy
{
/// <summary>
/// 服务提供器可以用来解析服务Services.GetService()
/// </summary>
public IServiceProvider Services { get; set; }
/// <summary>
/// 当前服务实例
/// </summary>
public object Target { get; set; }
/// <summary>
/// 方法
/// </summary>
/// <param name="method"></param>
/// <param name="args"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override object Invoke(MethodInfo method, object[] args)
{
var desc = Target.GetCustomAttribute<OperDescAttribute>(method.ToString(), true);
if (desc == null)
{
return Invoke(method, args);
}
else
{
Exception exception = default;
object result = default;
try
{
result = Invoke(method, args);
}
catch (Exception ex)
{
exception = ex;
}
WriteOperLog(method, args, desc, result, exception);
if (exception != null)
{
throw exception;
}
return result;//返回结果
}
object Invoke(MethodInfo method, object[] args)
{
//如果不带返回值
if (method.ReturnType == typeof(void))
{
return method.Invoke(Target, args);//直接返回
}
else
{
var result = method.Invoke(Target, args);
return result;//返回结果
}
}
}
/// <summary>
/// 异步无返回值
/// </summary>
/// <param name="method"></param>
/// <param name="args"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override async Task InvokeAsync(MethodInfo method, object[] args)
{
var desc = method.GetActualCustomAttribute<OperDescAttribute>(Target);
if (desc == null)
{
var task = method.Invoke(Target, args) as Task;
await task;
}
else
{
Exception exception = default;
try
{
var task = method.Invoke(Target, args) as Task;
await task;
}
catch (Exception ex)
{
exception = ex;
}
WriteOperLog(method, args, desc, null, exception);
if (exception != null)
{
throw exception;
}
}
}
/// <summary>
/// 异步带返回值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="method"></param>
/// <param name="args"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override async Task<T> InvokeAsyncT<T>(MethodInfo method, object[] args)
{
var desc = method.GetActualCustomAttribute<OperDescAttribute>(Target);
if (desc == null)
{
var taskT = method.Invoke(Target, args) as Task<T>;
var result = await taskT;
return result;//返回结果
}
else
{
T result = default;
//写入操作日志
Exception exception = null;
try
{
var taskT = method.Invoke(Target, args) as Task<T>;
result = await taskT;
}
catch (Exception ex)
{
exception = ex;
}
WriteOperLog(method, args, desc, result, exception);
if (exception != null)
{
throw exception;
}
else
{
return result;//返回结果
}
}
}
private static void WriteOperLog(MethodInfo method, object[] args, OperDescAttribute desc, object result, Exception exception)
{
//写入操作日志
var str = App.HttpContext?.Request?.Headers?.UserAgent;
ClientInfo clientInfo = null;
if (str.HasValue)
{
clientInfo = StaticParser.Parser.Parse(str);
}
StringBuilder stringBuilder = new();
if (desc.IsRecordPar)
{
var parameters = method.GetParameters();
var jsonParameters = parameters.Select((p, i) => $"\"{p.Name}\": {args[i].ToJsonString()}");
stringBuilder.Append("{");
stringBuilder.Append(string.Join(", ", jsonParameters));
stringBuilder.Append("}");
}
var paramJson = stringBuilder.ToString();
var resultJson = desc.IsRecordPar ? result?.ToJsonString() : null;
//操作日志表实体
var log = new SysOperateLog
{
Name = desc.Description,
Category = desc.Catcategory,
ExeStatus = LogConst.LOG_SUCCESS,
OpIp = App.HttpContext?.Connection?.RemoteIpAddress?.MapToIPv4().ToString(),
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
OpTime = SysDateTimeExtensions.CurrentDateTime,
OpAccount = UserManager.UserAccount,
ReqUrl = "",
ReqMethod = LogConst.LOG_REQMETHOD,
ResultJson = resultJson,
ClassName = method.ReflectedType.Name,
MethodName = method.Name,
ParamJson = paramJson,
VerificatId = UserManager.VerificatId.ToLong(),
};
//如果异常不为空
if (exception != null)
{
log.ExeStatus = LogConst.LOG_FAIL;//操作状态为失败
log.ExeMessage = exception.Source + ":" + exception.Message + Environment.NewLine + exception.StackTrace;
}
DbContext.Db.InsertableWithAttr(log).ExecuteCommand();//入库
}
}

View File

@@ -1,37 +1,38 @@
//------------------------------------------------------------------------------
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 设备在线状态
/// 权限操作常量
/// </summary>
public enum DeviceStatusEnum
public class AdminConst
{
/// <summary>
/// 初始化
/// </summary>
Default = 0,
#region
/// <summary>
/// 在线
/// 禁用操作
/// </summary>
OnLine = 1,
public const string Disable = "禁用";
/// <summary>
/// 离线
/// 启用操作
/// </summary>
OffLine = 2,
public const string Enable = "启用";
/// <summary>
/// 暂停
/// 用户授权操作
/// </summary>
Pause = 3,
}
public const string GrantRole = "授权";
#endregion
}

View File

@@ -0,0 +1,83 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// Cache常量
/// </summary>
public class CacheConst
{
/// <summary>
/// 登录验证码缓存Key
/// </summary>
public const string LOGIN_CAPTCHA = "LOGIN_CAPTCHA";
/// <summary>
/// 配置缓存Key
/// </summary>
public const string SYS_CONFIGCATEGORY = "SYS_CONFIGCATEGORY";
#region OpenApi
/// <summary>
/// OpenApi用户表缓存Key
/// </summary>
public const string CACHE_OPENAPIUSER = "CACHE_OPENAPIUSER";
/// <summary>
/// OpenApi关系缓存Key
/// </summary>
public const string CACHE_OPENAPIUSERACCOUNT = "CACHE_OPENAPIUSERACCOUNT";
/// <summary>
/// UserVerificat缓存Key
/// </summary>
public const string CACHE_OPENAPIUSERVERIFICAT = "CACHE_OPENAPIUSERVERIFICAT";
/// <summary>
/// UserVerificat缓存Key
/// </summary>
public const string CACHE_USERVERIFICAT = "CACHE_USERVERIFICAT";
#endregion OpenApi
/// <summary>
/// 用户表缓存Key
/// </summary>
public const string CACHE_SYSUSER = "CACHE_SYSUSER";
/// <summary>
/// 用户表缓存Key
/// </summary>
public const string CAHCE_SYSUSERACCOUNT = "CAHCE_SYSUSERACCOUNT";
/// <summary>
/// 关系表缓存Key
/// </summary>
public const string CACHE_SYSRELATION = "CACHE_SYSRELATION";
/// <summary>
/// 资源表缓存Key
/// </summary>
public const string CACHE_SYSRESOURCE = "CACHE_SYSRESOURCE";
/// <summary>
/// 角色表缓存Key
/// </summary>
public const string CACHE_SYSROLE = "CACHE_SYSROLE";
}

View File

@@ -0,0 +1,61 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 其他分类常量
/// </summary>
public static class CateGoryConst
{
/// <summary>
/// ThingsGateway.Admin
/// </summary>
public const string ThingsGatewayAdmin = "ThingsGateway.Admin";
/// <summary>
/// ThingsGateway.OpenApi
/// </summary>
public const string ThingsGatewayOpenApi = "ThingsGateway.OpenApi";
#region
/// <summary>
/// 用户主页
/// </summary>
public const string Relation_SYS_USER_DEFAULTRAZOR = "Relation_SYS_USER_DEFAULTRAZOR";
/// <summary>
/// 用户工作台数据
/// </summary>
public const string Relation_SYS_USER_WORKBENCH_DATA = "SYS_USER_WORKBENCH_DATA";
/// <summary>
/// 角色有哪些权限
/// </summary>
public const string Relation_SYS_ROLE_HAS_PERMISSION = "SYS_ROLE_HAS_PERMISSION";
/// <summary>
/// 角色有哪些资源
/// </summary>
public const string Relation_SYS_ROLE_HAS_RESOURCE = "SYS_ROLE_HAS_RESOURCE";
/// <summary>
/// 用户有哪些角色
/// </summary>
public const string Relation_SYS_USER_HAS_ROLE = "SYS_USER_HAS_ROLE";
#endregion
}

View File

@@ -0,0 +1,92 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 配置常量
/// </summary>
public static class ConfigConst
{
/// <summary>
/// 系统固定配置
/// </summary>
public const string SYS_CONFIGBASEDEFAULT = "SYS_CONFIGBASEDEFAULT";
/// <summary>
/// 其他自定义配置
/// </summary>
public const string SYS_CONFIGOTHER = "SYS_CONFIGOTHER";
#region config
/// <summary>
/// 版权标识
/// </summary>
public const string CONFIG_COPYRIGHT = "CONFIG_COPYRIGHT";
/// <summary>
/// 版权跳转url
/// </summary>
public const string CONFIG_COPYRIGHT_URL = "CONFIG_COPYRIGHT_URL";
/// <summary>
/// 登录验证码开关
/// </summary>
public const string CONFIG_CAPTCHA_OPEN = "CONFIG_CAPTCHA_OPEN";
/// <summary>
/// 默认用户密码
/// </summary>
public const string CONFIG_PASSWORD = "CONFIG_PASSWORD";
/// <summary>
/// 登录界面的介绍文本
/// </summary>
public const string CONFIG_REMARK = "CONFIG_REMARK";
/// <summary>
/// 单用户登录开关
/// </summary>
public const string CONFIG_SINGLE_OPEN = "CONFIG_SINGLE_OPEN";
/// <summary>
/// swagger用户
/// </summary>
public const string CONFIG_SWAGGER_NAME = "CONFIG_SWAGGER_NAME";
/// <summary>
/// swagger密码
/// </summary>
public const string CONFIG_SWAGGER_PASSWORD = "CONFIG_SWAGGER_PASSWORD";
/// <summary>
/// 系统标题
/// </summary>
public const string CONFIG_TITLE = "CONFIG_TITLE";
/// <summary>
/// 系统登录过期时间
/// </summary>
public const string CONFIG_VERIFICAT_EXPIRES = "CONFIG_VERIFICAT_EXPIRES";
/// <summary>
/// Swagger是否需要登录
/// </summary>
public const string CONFIG_SWAGGERLOGIN_OPEN = "CONFIG_SWAGGERLOGIN_OPEN";
#endregion
}

View File

@@ -0,0 +1,47 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 事件总线常量
/// </summary>
public class EventSubscriberConst
{
/// <summary>
/// 清除用户缓存
/// </summary>
public const string ClearUserCache = "清除用户缓存";
/// <summary>
/// 页面登录
/// </summary>
public const string Login = "登录";
/// <summary>
/// OpenApi登录
/// </summary>
public const string LoginOpenApi = "OpenApi登录";
/// <summary>
/// 后台登出
/// </summary>
public const string Logout = "退出";
/// <summary>
/// OpenApi登出
/// </summary>
public const string LogoutOpenApi = "OpenApi退出";
}

View File

@@ -1,23 +1,24 @@
//------------------------------------------------------------------------------
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Foundation;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 能对适配器做配置的客户端
/// 通讯器常量
/// </summary>
public interface IAdapterObject
public class HubConst
{
/// <summary>
/// 设置数据处理适配器
/// 系统HubUrl
/// </summary>
/// <param name="adapter">适配器</param>
void SetDataHandlingAdapter(DataHandlingAdapter adapter);
}
public const string SysHubUrl = "/hubs/thingsgateway";
}

View File

@@ -0,0 +1,70 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 日志常量
/// </summary>
public class LogConst
{
#region
/// <summary>
/// 登录
/// </summary>
public const string LOG_LOGIN = "LOGIN";
/// <summary>
/// 登出
/// </summary>
public const string LOG_LOGOUT = "LOGOUT";
/// <summary>
/// 第三方登录
/// </summary>
public const string LOG_OPENAPILOGIN = "OPENAPILOGIN";
/// <summary>
/// 第三方登出
/// </summary>
public const string LOG_OPENAPILOGOUT = "OPENAPILOGOUT";
/// <summary>
/// 第三方操作来源
/// </summary>
public const string LOG_OPENAPIOPERATE = "OPENAPIOPERATE";
/// <summary>
/// 操作分类
/// </summary>
public const string LOG_OPERATE = "OPERATE";
/// <summary>
/// 内部操作来源
/// </summary>
public const string LOG_REQMETHOD = "BLAZORSERVER";
/// <summary>
/// 操作成功
/// </summary>
public const string LOG_SUCCESS = "SUCCESS";
/// <summary>
/// 操作失败
/// </summary>
public const string LOG_FAIL = "FAIL";
#endregion
}

View File

@@ -1,20 +1,24 @@
//------------------------------------------------------------------------------
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Foundation;
namespace ThingsGateway.Admin.Core;
/// <inheritdoc/>
public interface IDtu : ITcpService
/// <summary>
/// 资源表常量
/// </summary>
public class ResourceConst
{
/// <summary>
/// 心跳检测(大写16进制字符串)
/// 系统内置编码
/// </summary>
public string HeartbeatHexString { get; set; }
}
public const string System = "system";
}

View File

@@ -0,0 +1,44 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 角色常量
/// </summary>
public class RoleConst
{
/// <summary>
/// 超级管理员
/// </summary>
public const string SuperAdmin = "superAdmin";
#region
/// <summary>
/// 角色有哪些权限
/// </summary>
public const string Relation_SYS_ROLE_HAS_PERMISSION = "SYS_ROLE_HAS_PERMISSION";
/// <summary>
/// 角色有哪些资源
/// </summary>
public const string Relation_SYS_ROLE_HAS_RESOURCE = "SYS_ROLE_HAS_RESOURCE";
/// <summary>
/// 用户有哪些角色
/// </summary>
public const string Relation_SYS_USER_HAS_ROLE = "SYS_USER_HAS_ROLE";
#endregion
}

View File

@@ -0,0 +1,18 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;

View File

@@ -0,0 +1,141 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using ThingsGateway.Admin.Core;
using ThingsGateway.Admin.Core.JsonExtensions;
using Yitter.IdGenerator;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 扩展方法
/// </summary>
public class PermissionUtil
{
/// <summary>
/// 获取WebApi授权树
/// </summary>
/// <returns></returns>
public static List<OpenApiPermissionTreeSelector> OpenApiPermissionTreeSelector()
{
var cacheKey = $"{nameof(OpenApiPermissionTreeSelector)}-{CultureInfo.CurrentUICulture.Name}";
List<OpenApiPermissionTreeSelector> displayName = CacheStatic.Cache.GetOrCreate(cacheKey, entry =>
{
List<OpenApiPermissionTreeSelector> openApiGroups = new();
var controllerTypes = App.EffectiveTypes
.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(OpenApiPermissionAttribute), false));
foreach (var controller in controllerTypes)
{
var GroupName = controller.GetCustomAttribute<ApiDescriptionSettingsAttribute>().GroupName;
if (GroupName == CateGoryConst.ThingsGatewayOpenApi)
{
var Description = controller.GetCustomAttribute<DescriptionAttribute>().Description;
var parid = YitIdHelper.NextId();
OpenApiPermissionTreeSelector openApiGroup = new() { ApiName = Description, Id = parid, PermissionName = Description };
var routeName = "/" + controller.GetCustomAttribute<RouteAttribute>().Template;
//获取所有方法
var menthods = controller.GetMethods();
//遍历方法
foreach (var menthod in menthods)
{
//获取忽略数据权限特性
var ignoreOpenApiPermission = menthod.GetCustomAttribute<IgnoreOpenApiPermissionAttribute>();
if (ignoreOpenApiPermission == null)//如果是空的代表需要数据权限
{
//获取接口描述
var description = menthod.GetCustomAttribute<DescriptionAttribute>();
if (description != null)
{
//默认路由名称
var apiRoute = menthod.Name;
//获取get特性
var requestGet = menthod.GetCustomAttribute<HttpGetAttribute>();
if (requestGet != null)//如果是get方法
apiRoute = requestGet.Template;
else
{
//获取post特性
var requestPost = menthod.GetCustomAttribute<HttpPostAttribute>();
if (requestPost != null)//如果是post方法
apiRoute = requestPost.Template;
}
apiRoute = routeName + $"/{apiRoute}";
var apiName = description.Description;//如果描述不为空则接口名称用描述的名称
//合并
var permissionName = apiRoute + $"[{apiName}]";
//添加到权限列表
openApiGroup.Children.Add(new OpenApiPermissionTreeSelector
{
Id = YitIdHelper.NextId(),
ParentId = parid,
ApiName = apiName,
ApiRoute = apiRoute,
PermissionName = permissionName
});
}
}
}
openApiGroups.Add(openApiGroup);
}
}
return openApiGroups;
}, false);
return displayName;
}
/// <summary>
/// 获取全部页面权限内容
/// </summary>
/// <param name="routers"></param>
/// <returns></returns>
public static List<PermissionTreeSelector> PermissionTreeSelector(List<string> routers)
{
var cacheKey = $"{nameof(PermissionTreeSelector)}-{CultureInfo.CurrentUICulture.Name}-{routers.ToJsonString()}";
List<PermissionTreeSelector> displayName = CacheStatic.Cache.GetOrCreate(cacheKey, entry =>
{
List<PermissionTreeSelector> permissions = new();//权限列表
// 获取所有需要数据权限的控制器
var controllerTypes = App.EffectiveTypes.
Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass
&& u.IsDefined(typeof(AuthorizeAttribute), false)
&& u.IsDefined(typeof(Microsoft.AspNetCore.Components.RouteAttribute), false));
foreach (var controller in controllerTypes)
{
//获取数据权限特性
var routeName = controller.GetCustomAttribute<Microsoft.AspNetCore.Components.RouteAttribute>()?.Template;
if (routeName == null)
continue;
if (routers.Contains(routeName))
{
var apiRoute = $"{routeName}";
permissions.Add(new() { ApiRoute = apiRoute });
}
}
return permissions;
}, false
);
return displayName;
}
}

View File

@@ -0,0 +1,126 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.Text;
using ThingsGateway.Admin.Core;
using ThingsGateway.Admin.Core.JsonExtensions;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 种子数据工具类
/// </summary>
public class SeedDataUtil
{
/// <summary>
/// json转化为种子列表
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="jsonName"></param>
/// <returns></returns>
public static List<T> GetSeedData<T>(string jsonName)
{
var seedData = new List<T>();//种子数据结果
var basePath = AppContext.BaseDirectory;//获取项目目录
var json = basePath.CombinePath("SeedData", "Json", jsonName);//获取文件路径
var dataString = ReadFile(json);//读取文件
if (!string.IsNullOrEmpty(dataString))//如果有内容
{
//字段没有数据的替换成null
dataString = dataString.Replace("\"\"", "null");
//将json字符串转为实体这里extjson可以正常转换为字符串
var seedDataRecord = dataString.ToJsonWithT<SeedDataRecords<T>>();
//遍历seedDataRecord
for (int i = 0; i < seedDataRecord.Records.Count; i++)
{
#region ExtJosn
//获取extjson属性
var propertyExtJosn = typeof(T).GetProperty(nameof(PrimaryKeyEntity.ExtJson));
if (propertyExtJosn != null)
{
//获取extjson的值
var extJson = propertyExtJosn.GetValue(seedDataRecord.Records[i])?.ToString();
// 如果extjson不为空并且包含NullableDictionary表示序列化失败了
if (!string.IsNullOrEmpty(extJson) && extJson.Contains("NullableDictionary"))
{
//设置extjson为seedDataRecord对应的值
extJson = propertyExtJosn.GetValue(seedDataRecord.Records[i])?.ToString();
//seedDataRecord赋值seedDataRecord的extjson
propertyExtJosn.SetValue(seedDataRecord.Records[i], extJson);
}
}
#endregion ExtJosn
#region ConfigValue
//获取extjson属性
var propertyConfigValue = typeof(T).GetProperty(nameof(SysConfig.ConfigValue));
if (propertyConfigValue != null)
{
//获取extjson的值
var configValue = propertyConfigValue.GetValue(seedDataRecord.Records[i])?.ToString();
// 如果extjson不为空并且包含NullableDictionary表示序列化失败了
if (!string.IsNullOrEmpty(configValue) && configValue.Contains("NullableDictionary"))
{
//设置extjson为seedDataRecord对应的值
configValue = propertyConfigValue.GetValue(seedDataRecord.Records[i])?.ToString();
//seedDataRecord赋值seedDataRecord的extjson
propertyConfigValue.SetValue(seedDataRecord.Records[i], configValue);
}
}
#endregion ConfigValue
}
//种子数据赋值
seedData = seedDataRecord.Records;
}
return seedData;
}
/// <summary>
/// 读取文件
/// </summary>
/// <param name="Path"></param>
/// <returns></returns>
public static string ReadFile(string Path)
{
if (!File.Exists(Path))
{
return null;
}
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
StreamReader streamReader = new(Path, Encoding.GetEncoding("utf-8"));
string result = streamReader.ReadToEnd();
streamReader.Close();
streamReader.Dispose();
return result;
}
}
/// <summary>
/// 种子数据格式实体类,遵循Navicat导出json格式
/// </summary>
/// <typeparam name="T"></typeparam>
public class SeedDataRecords<T>
{
/// <summary>
/// 数据
/// </summary>
public List<T> Records { get; set; }
}

View File

@@ -1,32 +1,26 @@
//------------------------------------------------------------------------------
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Gateway.Application;
using UAParser;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 用户权限
/// 单例Parser
/// </summary>
public enum ProtectTypeEnum
public class StaticParser
{
/// <summary>
/// 只读
/// 单例
/// </summary>
ReadOnly,
/// <summary>
/// 读写
/// </summary>
ReadWrite,
/// <summary>
/// 只写
/// </summary>
WriteOnly,
}
public static Parser Parser { get; } = Parser.GetDefault();
}

View File

@@ -0,0 +1,69 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.Schedule;
using Microsoft.Extensions.DependencyInjection;
namespace ThingsGateway.Admin.Application;
/// <inheritdoc cref="IJobPersistence"/>
public class JobPersistence : IJobPersistence
{
private readonly IServiceScope _serviceScope;
/// <inheritdoc/>
public JobPersistence(IServiceProvider serviceProvider)
{
_serviceScope = serviceProvider.CreateScope();
}
/// <summary>
/// 作业调度服务启动时
/// </summary>
/// <returns></returns>
public IEnumerable<SchedulerBuilder> Preload()
{
// 获取所有定义的作业
var allJobs = App.EffectiveTypes.ScanToBuilders().ToList();
return allJobs;
}
/// <summary>
/// 作业计划初始化通知
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public SchedulerBuilder OnLoading(SchedulerBuilder builder)
{
return builder;
}
/// <inheritdoc/>
public void Dispose()
{
_serviceScope?.Dispose();
}
/// <inheritdoc/>
public void OnChanged(PersistenceContext context)
{
}
/// <inheritdoc/>
public void OnTriggerChanged(PersistenceTriggerContext context)
{
}
}

View File

@@ -0,0 +1,34 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.Schedule;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 清理日志作业任务
/// </summary>
[JobDetail("job_log", Description = "清理访问/操作日志", GroupName = "default", Concurrent = false)]
[Daily(TriggerId = "trigger_log", Description = "清理访问/操作日志", RunOnStart = true)]
public class LogJob : IJob
{
/// <inheritdoc/>
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
var db = DbContext.Db.CopyNew();
var daysAgo = 30; // 删除30天以前
await db.Deleteable<SysVisitLog>().Where(u => u.CreateTime < SysDateTimeExtensions.CurrentDateTime.AddDays(-daysAgo)).ExecuteCommandAsync(); // 删除访问日志
await db.Deleteable<SysOperateLog>().Where(u => u.CreateTime < SysDateTimeExtensions.CurrentDateTime.AddDays(-daysAgo)).ExecuteCommandAsync(); // 删除操作日志
}
}

View File

@@ -0,0 +1,51 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 登录事件参数
/// </summary>
public class LoginOpenApiEvent
{
/// <summary>
/// 时间
/// </summary>
public DateTime DateTime = SysDateTimeExtensions.CurrentDateTime;
/// <summary>
/// 登录设备
/// </summary>
public AuthDeviceTypeEnum Device { get; set; }
/// <summary>
/// 过期时间(分)
/// </summary>
public int Expire { get; set; }
/// <summary>
/// Ip地址
/// </summary>
public string Ip { get; set; }
/// <summary>
/// 用户信息
/// </summary>
public OpenApiUser OpenApiUser { get; set; }
/// <summary>
/// 验证Id
/// </summary>
public long VerificatId { get; set; }
}

View File

@@ -0,0 +1,54 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Furion.EventBus;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 认证模块事件总线
/// </summary>
public class OpenApiAuthEventSubscriber : IEventSubscriber, ISingleton
{
/// <summary>
/// 登录事件
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
[EventSubscribe(EventSubscriberConst.LoginOpenApi)]
public async Task LoginOpenApi(EventHandlerExecutingContext context)
{
LoginOpenApiEvent loginEvent = (LoginOpenApiEvent)context.Source.Payload;//获取参数
OpenApiUser openApiUser = loginEvent.OpenApiUser;
var db = DbContext.Db.CopyNew();
#region ,
db.Tracking(openApiUser);//创建跟踪,只更新修改字段
openApiUser.LastLoginDevice = openApiUser.LatestLoginDevice;
openApiUser.LastLoginIp = openApiUser.LatestLoginIp;
openApiUser.LastLoginTime = openApiUser.LatestLoginTime;
openApiUser.LatestLoginDevice = loginEvent.Device.ToString();
openApiUser.LatestLoginIp = loginEvent.Ip;
openApiUser.LatestLoginTime = loginEvent.DateTime;
#endregion ,
//更新用户信息
if (await db.UpdateableWithAttr(openApiUser).ExecuteCommandAsync() > 0)
{
CacheStatic.Cache.Set(CacheConst.CACHE_OPENAPIUSER + openApiUser.Id, openApiUser, false); //更新Cache信息
}
}
}

View File

@@ -0,0 +1,33 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 登录输入参数
/// </summary>
public class LoginOpenApiInput
{
/// <summary>
/// 账号
///</summary>
[Required(ErrorMessage = "账号不能为空"), MinLength(3, ErrorMessage = "账号不能少于4个字符")]
public string Account { get; set; }
/// <summary>
/// 密码
///</summary>
[Required(ErrorMessage = "密码不能为空"), MinLength(3, ErrorMessage = "密码不能少于3个字符")]
public string Password { get; set; }
}

View File

@@ -0,0 +1,24 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 登录返回参数
/// </summary>
public class LoginOpenApiOutput : BaseLoginOutput
{
/// <summary>
/// TOKEN
/// </summary>
public string Token { get; set; }
}

View File

@@ -0,0 +1,33 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 登录服务
/// </summary>
public interface IOpenApiAuthService : ITransient
{
/// <summary>
/// 登录
/// </summary>
/// <param name="input">登录参数</param>
/// <returns>Token信息</returns>
Task<LoginOpenApiOutput> LoginOpenApiAsync(LoginOpenApiInput input);
/// <summary>
/// 登出
/// </summary>
/// <returns></returns>
Task LogoutAsync();
}

View File

@@ -0,0 +1,173 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.DataEncryption;
using Furion.DependencyInjection;
using Furion.EventBus;
using Furion.FriendlyException;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using ThingsGateway.Admin.Core;
using Yitter.IdGenerator;
namespace ThingsGateway.Admin.Application;
/// <inheritdoc cref="IOpenApiAuthService"/>
public class OpenApiAuthService : IOpenApiAuthService, ITransient
{
private readonly IConfigService _configService;
private readonly IEventPublisher _eventPublisher;
private readonly IOpenApiUserService _openApiUserService;
private readonly IVerificatService _verificatService;
/// <inheritdoc cref="IOpenApiAuthService"/>
public OpenApiAuthService(
IEventPublisher eventPublisher,
IOpenApiUserService openApiUserService,
IVerificatService verificatService,
IConfigService configService)
{
_verificatService = verificatService;
_eventPublisher = eventPublisher;
_openApiUserService = openApiUserService;
_configService = configService;
}
/// <inheritdoc/>
public async Task<LoginOpenApiOutput> LoginOpenApiAsync(LoginOpenApiInput input)
{
var password = input.Password;
var userInfo = await _openApiUserService.GetUserByAccountAsync(input.Account);//获取用户信息
if (userInfo == null) throw Oops.Bah("用户不存在");//用户不存在
if (userInfo.Password != password) throw Oops.Bah("账号密码错误");//账号密码错误
return await PrivateLoginOpenApiAsync(userInfo);
}
/// <inheritdoc/>
public async Task LogoutAsync()
{
//获取用户信息
var userinfo = await _openApiUserService.GetUserByAccountAsync(UserManager.UserAccount);
if (userinfo != null)
{
LoginOpenApiEvent loginEvent = new()
{
Ip = App.HttpContext.GetRemoteIpAddressToIPv4(),
OpenApiUser = userinfo,
VerificatId = UserManager.VerificatId.ToLong(),
};
await RemoveVerificatFromCacheAsync(loginEvent);
}
}
private async Task<List<VerificatInfo>> GetVerificatInfos(long userId)
{
List<VerificatInfo> verificatInfos = await _verificatService.GetOpenApiVerificatIdAsync(userId);
return verificatInfos;
}
private async Task<LoginOpenApiOutput> PrivateLoginOpenApiAsync(OpenApiUser openApiUser)
{
if (openApiUser.UserEnable == false) throw Oops.Bah("账号已停用");//账号冻结
var sessionid = YitIdHelper.NextId();
var expire = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_VERIFICAT_EXPIRES)).ConfigValue.ToInt();
//生成Token
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
{
{ClaimConst.UserId, openApiUser.Id},
{ClaimConst.Account, openApiUser.Account},
{ ClaimConst.VerificatId, sessionid.ToString()},
{ ClaimConst.IsOpenApi, true},
}, expire);
// 生成刷新Token令牌
var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, expire * 2);
// 设置Swagger自动登录
App.HttpContext.SigninToSwagger(accessToken);
// 设置响应报文头
App.HttpContext.SetTokensOfResponseHeaders(accessToken, refreshToken);
//登录事件参数
var logingEvent = new LoginOpenApiEvent
{
Ip = App.HttpContext.GetRemoteIpAddressToIPv4(),
Device = AuthDeviceTypeEnum.Api,
Expire = expire,
OpenApiUser = openApiUser,
VerificatId = sessionid
};
await WriteVerificatToCacheAsync(logingEvent);//写入verificat到cache
await _eventPublisher.PublishAsync(EventSubscriberConst.LoginOpenApi, logingEvent); //发布登录事件总线
//返回结果
return new LoginOpenApiOutput { VerificatId = sessionid, Token = accessToken, Account = openApiUser.Account };
}
private async Task RemoveVerificatFromCacheAsync(LoginOpenApiEvent loginEvent)
{
//获取verificat列表
var verificatInfos = await GetVerificatInfos(loginEvent.OpenApiUser.Id);
if (verificatInfos != null)
{
//获取当前用户的verificat
var verificat = verificatInfos.Where(it => it.Id == loginEvent.VerificatId).FirstOrDefault();
if (verificat != null)
verificatInfos.Remove(verificat);
//更新verificat列表
await _verificatService.SetOpenApiVerificatIdAsync(loginEvent.OpenApiUser.Id, verificatInfos);
}
await App.HttpContext?.SignOutAsync();
App.HttpContext?.SignoutToSwagger();
}
private async Task WriteVerificatToCacheAsync(LoginOpenApiEvent loginEvent)
{
//获取verificat列表
List<VerificatInfo> verificatInfos = await GetVerificatInfos(loginEvent.OpenApiUser.Id);
var verificatTimeout = loginEvent.DateTime.AddMinutes(loginEvent.Expire);
//生成verificat信息
var verificatInfo = new VerificatInfo
{
Device = loginEvent.Device.ToString(),
Expire = loginEvent.Expire,
VerificatTimeout = verificatTimeout,
Id = loginEvent.VerificatId,
UserId = loginEvent.OpenApiUser.Id,
};
if (verificatInfos != null)
{
bool isSingle = false;//默认不开启单用户登录
var singleConfig = await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SINGLE_OPEN);//获取系统单用户登录选项
if (singleConfig != null) isSingle = singleConfig.ConfigValue.ToBoolean();//如果配置不为空则设置单用户登录选项为系统配置的值
//判断是否单用户登录
if (isSingle)
{
verificatInfos = verificatInfos.ToList();//去掉当前登录类型的verificat
verificatInfos.Add(verificatInfo);//添加到列表
}
else
{
verificatInfos.Add(verificatInfo);
}
}
else
{
verificatInfos = new List<VerificatInfo> { verificatInfo };//直接就一个
}
//添加到verificat列表
await _verificatService.SetOpenApiVerificatIdAsync(loginEvent.OpenApiUser.Id, verificatInfos);
}
}

View File

@@ -0,0 +1,47 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 会话分页查询
/// </summary>
public class OpenApiSessionPageInput : BasePageInput
{
/// <summary>
/// 账号
/// </summary>
[Description("账号")]
public string Account { get; set; }
/// <summary>
/// 最新登录IP
/// </summary>
[Description("最新登录IP")]
public string LatestLoginIp { get; set; }
}
/// <summary>
/// 退出参数
/// </summary>
public class OpenApiExitVerificatInput : BaseIdInput
{
/// <summary>
/// 验证ID列表
/// </summary>
[Required(ErrorMessage = "VerificatIds不能为空")]
public List<long> VerificatIds { get; set; }
}

View File

@@ -0,0 +1,57 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 会话输出
/// </summary>
public class OpenApiSessionOutput : PrimaryKeyEntity
{
/// <summary>
/// 账号
///</summary>
[Description("账号")]
[DataTable(Order = 1, IsShow = true, Sortable = true)]
public virtual string Account { get; set; }
/// <summary>
/// 最新登录ip
///</summary>
[Description("最新登录ip")]
[DataTable(Order = 2, IsShow = true, Sortable = true)]
public string LatestLoginIp { get; set; }
/// <summary>
/// 最新登录时间
///</summary>
[Description("最新登录时间")]
[DataTable(Order = 3, IsShow = true, Sortable = true)]
public DateTime? LatestLoginTime { get; set; }
/// <summary>
/// 令牌数量
/// </summary>
[Description("令牌数量")]
[DataTable(Order = 4, IsShow = true, Sortable = true)]
public int VerificatCount { get; set; }
/// <summary>
/// 令牌信息集合
/// </summary>
[Description("令牌列表")]
public List<VerificatInfo> VerificatSignList { get; set; }
}

View File

@@ -0,0 +1,42 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 会话管理服务
/// </summary>
public interface IOpenApiSessionService : ITransient
{
/// <summary>
/// 强退会话
/// </summary>
/// <param name="input">用户ID</param>
Task ExitSessionAsync(long input);
/// <summary>
/// 强退cookie
/// </summary>
/// <param name="input">cookie列表</param>
Task ExitVerificatAsync(OpenApiExitVerificatInput input);
/// <summary>
/// B端会话分页查询
/// </summary>
/// <param name="input">查询参数</param>
/// <returns>B端会话列表</returns>
Task<SqlSugarPagedList<OpenApiSessionOutput>> PageAsync(OpenApiSessionPageInput input);
}

View File

@@ -0,0 +1,108 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using SqlSugar;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// <inheritdoc cref="IOpenApiSessionService"/>
/// </summary>
[Injection(Proxy = typeof(OperDispatchProxy))]
public class OpenApiSessionService : DbRepository<OpenApiUser>, IOpenApiSessionService
{
private readonly IVerificatService _verificatService;
/// <inheritdoc cref="IOpenApiSessionService"/>
public OpenApiSessionService(IVerificatService verificatService)
{
_verificatService = verificatService;
}
/// <inheritdoc/>
[OperDesc("强退OPENAPI会话")]
public async Task ExitSessionAsync(long input)
{
//从列表中删除
await _verificatService.SetOpenApiVerificatIdAsync(input, new());
}
/// <inheritdoc/>
[OperDesc("强退OPENAPI令牌")]
public async Task ExitVerificatAsync(OpenApiExitVerificatInput input)
{
//获取该用户的verificat信息
List<VerificatInfo> verificatInfos = await _verificatService.GetOpenApiVerificatIdAsync(input.Id);
//当前需要踢掉用户的verificat
var setVerificats = verificatInfos.Where(it => !input.VerificatIds.Contains(it.Id)).ToList();
await _verificatService.SetOpenApiVerificatIdAsync(input.Id, setVerificats);//如果还有verificat则更新verificat
}
/// <summary>
/// 获取verificat剩余时间信息
/// </summary>
/// <param name="verificatInfos">verificat列表</param>
private void GetVerificatInfos(ref List<VerificatInfo> verificatInfos)
{
verificatInfos = verificatInfos.ToList();
verificatInfos.ForEach(it =>
{
var now = SysDateTimeExtensions.CurrentDateTime;
it.VerificatRemain = now.GetDiffTime(it.VerificatTimeout);//获取时间差
var verificatSecond = it.VerificatTimeout.AddMinutes(-it.Expire).ToLong();//颁发时间转为时间戳
var timeoutSecond = it.VerificatTimeout.ToLong();//过期时间转为时间戳
});
}
/// <inheritdoc/>
public async Task<SqlSugarPagedList<OpenApiSessionOutput>> PageAsync(OpenApiSessionPageInput input)
{
var query = Context.Queryable<OpenApiUser>()
.WhereIF(!string.IsNullOrEmpty(input.Account), it => it.Account.Contains(input.Account))//根据账号查询
.WhereIF(!string.IsNullOrEmpty(input.LatestLoginIp), it => it.LatestLoginIp.Contains(input.LatestLoginIp))//根据IP查询
.OrderBy(it => it.LatestLoginTime, OrderByType.Desc)
.Select<OpenApiSessionOutput>()
.Mapper(async it =>
{
var verificatInfos = await _verificatService.GetVerificatIdAsync(it.Id);
if (verificatInfos != null)
{
GetVerificatInfos(ref verificatInfos);//获取剩余时间
it.VerificatCount = verificatInfos.Count;//令牌数量
it.VerificatSignList = verificatInfos;//令牌列表
}
else
{
it.VerificatSignList = new();
}
});
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页
pageInfo.Records = pageInfo.Records.OrderByDescending(it => it.VerificatCount);
return pageInfo;
}
}

View File

@@ -0,0 +1,150 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using SqlSugar;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// Api授权资源树
/// </summary>
public class OpenApiPermissionTreeSelector
{
/// <summary>
/// 接口描述
/// </summary>
[Description("Api说明")]
public string ApiName { get; set; }
/// <summary>
/// 路由名称
/// </summary>
[Description("Api路径")]
public string ApiRoute { get; set; }
/// <summary>
/// 子节点
/// </summary>
public List<OpenApiPermissionTreeSelector> Children { get; set; } = new();
/// <summary>
/// ID
/// </summary>
public long Id { get; set; }
/// <summary>
/// 父ID
/// </summary>
public long ParentId { get; set; }
/// <summary>
/// 权限名称
/// </summary>
[Description("权限名称")]
public string PermissionName { get; set; }
/// <summary>
/// 多个树转列表
/// </summary>
public static List<OpenApiPermissionTreeSelector> TreeToList(IList<OpenApiPermissionTreeSelector> data)
{
List<OpenApiPermissionTreeSelector> list = new();
foreach (var item in data)
{
list.Add(item);
if (item.Children != null && item.Children.Count > 0)
{
list.AddRange(TreeToList(item.Children));
}
}
return list;
}
}
/// <summary>
/// 添加用户参数
/// </summary>
public class OpenApiUserAddInput : OpenApiUser
{
/// <summary>
/// 账号
/// </summary>
[Required(ErrorMessage = "账号不能为空"), MinLength(3, ErrorMessage = "账号不能少于4个字符")]
public override string Account { get; set; }
/// <summary>
/// 密码
/// </summary>
[Required(ErrorMessage = "密码不能为空"), MinLength(2, ErrorMessage = "密码不能少于3个字符")]
public override string Password { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
public override bool UserEnable { get; set; } = true;
}
/// <summary>
/// 编辑用户参数
/// </summary>
public class OpenApiUserEditInput : OpenApiUser
{
/// <summary>
/// 账号
/// </summary>
[Required(ErrorMessage = "账号不能为空"), MinLength(3, ErrorMessage = "账号不能少于4个字符")]
public override string Account { get; set; }
/// <summary>
/// Id
/// </summary>
[MinValue(1, ErrorMessage = "Id不能为空")]
public override long Id { get; set; }
/// <summary>
/// 密码
/// </summary>
[Required(ErrorMessage = "密码不能为空"), MinLength(2, ErrorMessage = "密码不能少于3个字符")]
public override string Password { get; set; }
}
/// <summary>
/// 用户分页查询参数
/// </summary>
public class OpenApiUserPageInput : BasePageInput
{
/// <summary>
/// 动态查询条件
/// </summary>
public Expressionable<SysUser> Expression { get; set; }
}
/// <summary>
/// 用户授权参数
/// </summary>
public class OpenApiUserGrantPermissionInput
{
/// <summary>
/// Id
/// </summary>
[Required(ErrorMessage = "Id不能为空")]
public long? Id { get; set; }
/// <summary>
/// 授权权限信息
/// </summary>
[Required(ErrorMessage = "PermissionList不能为空")]
public List<string> PermissionList { get; set; }
}

View File

@@ -0,0 +1,106 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 用户服务
/// </summary>
public interface IOpenApiUserService : ITransient
{
/// <summary>
/// 添加用户
/// </summary>
/// <param name="input">添加参数</param>
/// <returns></returns>
Task AddAsync(OpenApiUserAddInput input);
/// <summary>
/// 删除用户
/// </summary>
/// <param name="input">Id列表</param>
/// <returns></returns>
Task DeleteAsync(params long[] input);
/// <summary>
/// 从cache中删除用户信息
/// </summary>
/// <param name="ids">用户ID列表</param>
void DeleteUserFromCache(params long[] ids);
/// <summary>
/// 禁用用户
/// </summary>
/// <param name="input">用户Id</param>
/// <returns></returns>
Task DisableUserAsync(long input);
/// <summary>
/// 编辑
/// </summary>
/// <param name="input">编辑参数</param>
/// <returns></returns>
Task EditAsync(OpenApiUserEditInput input);
/// <summary>
/// 启用用户
/// </summary>
/// <param name="input">用户Id</param>
/// <returns></returns>
Task EnableUserAsync(long input);
/// <summary>
///根据用户账号获取用户ID
/// </summary>
/// <param name="account">用户账号</param>
/// <returns></returns>
Task<long> GetIdByAccountAsync(string account);
/// <summary>
/// 根据账号获取用户信息
/// </summary>
/// <param name="account">用户名</param>
/// <returns>用户信息</returns>
Task<OpenApiUser> GetUserByAccountAsync(string account);
/// <summary>
/// 根据ID获取用户信息
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
Task<OpenApiUser> GetUsertByIdAsync(long Id);
/// <summary>
/// 给用户授权
/// </summary>
/// <param name="input">授权参数</param>
/// <returns></returns>
Task GrantRoleAsync(OpenApiUserGrantPermissionInput input);
/// <summary>
/// 获取用户拥有权限,返回的是服务方法名称
/// </summary>
/// <param name="input">用户ID</param>
/// <returns></returns>
Task<List<string>> OwnPermissionsAsync(BaseIdInput input);
/// <summary>
/// 用户分页查询
/// </summary>
/// <param name="input">查询参数</param>
/// <returns>用户分页列表</returns>
Task<SqlSugarPagedList<OpenApiUser>> PageAsync(OpenApiUserPageInput input);
}

View File

@@ -0,0 +1,280 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DataEncryption;
using Furion.DependencyInjection;
using Furion.FriendlyException;
using Mapster;
using SqlSugar;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// <inheritdoc cref="IOpenApiUserService"/>
/// </summary>
[Injection(Proxy = typeof(OperDispatchProxy))]
public class OpenApiUserService : DbRepository<OpenApiUser>, IOpenApiUserService
{
private readonly IVerificatService _verificatService;
/// <inheritdoc cref="IOpenApiUserService"/>
public OpenApiUserService(
IVerificatService verificatService
)
{
_verificatService = verificatService;
}
/// <inheritdoc/>
[OperDesc("添加用户")]
public async Task AddAsync(OpenApiUserAddInput input)
{
var account_Id = await GetIdByAccountAsync(input.Account);
if (account_Id > 0)
throw Oops.Bah($"存在重复的账号:{input.Account}");
var openApiUser = input.Adapt<OpenApiUser>();//实体转换
await InsertAsync(openApiUser);//添加数据
}
/// <inheritdoc/>
[OperDesc("删除用户")]
public async Task DeleteAsync(params long[] ids)
{
//获取所有ID
if (ids.Length > 0)
{
var result = await DeleteByIdsAsync(ids.Cast<object>().ToArray());
if (result)
{
//从列表中删除
foreach (var id in ids)
{
await _verificatService.SetOpenApiVerificatIdAsync(id, new());
}
DeleteUserFromCache(ids);
}
}
}
/// <inheritdoc />
public void DeleteUserFromCache(params long[] ids)
{
var userIds = ids.Select(it => it.ToString()).ToArray();//id转string列表
List<OpenApiUser> openApiUsers = new();
foreach (var item in userIds)
{
var user = CacheStatic.Cache.Get<OpenApiUser>(CacheConst.CACHE_OPENAPIUSER + item, false);//获取用户列表
openApiUsers.Add(user);
CacheStatic.Cache.Remove(CacheConst.CACHE_OPENAPIUSER + item);
}
openApiUsers = openApiUsers.Where(it => it != null).ToList();//过滤掉不存在的
if (openApiUsers.Count > 0)
{
var accounts = openApiUsers.Select(it => it.Account).ToArray();//账号集合
foreach (var item in accounts)
{
//删除账号
CacheStatic.Cache.Remove(CacheConst.CACHE_OPENAPIUSERACCOUNT + item);
}
}
}
/// <inheritdoc/>
[OperDesc("禁用用户")]
public async Task DisableUserAsync(long input)
{
var openApiUser = await GetUsertByIdAsync(input);//获取用户信息
if (openApiUser != null)
{
if (await UpdateAsync(it => new OpenApiUser { UserEnable = false }, it => it.Id == input))
{
await _verificatService.SetOpenApiVerificatIdAsync(input, new());
DeleteUserFromCache(input);//从cache删除用户信息
}
}
}
/// <inheritdoc/>
[OperDesc("编辑用户")]
public async Task EditAsync(OpenApiUserEditInput input)
{
await CheckInputAsync(input);//检查参数
var exist = await GetUsertByIdAsync(input.Id);//获取用户信息
if (exist != null)
{
var openApiUser = input.Adapt<OpenApiUser>();//实体转换
openApiUser.Password = DESCEncryption.Encrypt(openApiUser.Password, DESCKeyConst.DESCKey);
if (await Context.Updateable(openApiUser).IgnoreColumns(it =>
new
{
//忽略更新字段
it.LastLoginDevice,
it.LastLoginIp,
it.LastLoginTime,
it.LatestLoginDevice,
it.LatestLoginIp,
it.LatestLoginTime
}).ExecuteCommandAsync() > 0)//修改数据
DeleteUserFromCache(openApiUser.Id);//用户缓存到cache
}
//编辑操作可能会修改用户密码等信息,认证时需要实时获取用户并验证
}
/// <inheritdoc/>
[OperDesc("启用用户")]
public async Task EnableUserAsync(long input)
{
//设置状态为启用
if (await UpdateAsync(it => new OpenApiUser { UserEnable = true }, it => it.Id == input))
DeleteUserFromCache(input);//从cache删除用户信息
}
/// <inheritdoc/>
public async Task<long> GetIdByAccountAsync(string account)
{
//先从Cache拿
var userId = CacheStatic.Cache.Get<long>(CacheConst.CACHE_OPENAPIUSERACCOUNT + account, false);
if (userId == 0)
{
//单查获取用户账号对应ID
userId = await GetFirstAsync(it => it.Account == account, it => it.Id);
if (userId != 0)
{
//插入Cache
CacheStatic.Cache.Set(CacheConst.CACHE_OPENAPIUSERACCOUNT + account, userId, false);
}
}
return userId;
}
/// <inheritdoc/>
public async Task<OpenApiUser> GetUserByAccountAsync(string account)
{
var userId = await GetIdByAccountAsync(account);//获取用户ID
if (userId > 0)
{
var openApiUser = await GetUsertByIdAsync(userId);//获取用户信息
if (openApiUser.Account == account)//这里做了比较用来限制大小写
return openApiUser;
else
return null;
}
else
{
return null;
}
}
/// <inheritdoc/>
public async Task<OpenApiUser> GetUsertByIdAsync(long Id)
{
//先从Cache拿需要获取新的对象避免操作导致缓存中对象改变
var openApiUser = CacheStatic.Cache.Get<OpenApiUser>(CacheConst.CACHE_OPENAPIUSER + Id.ToString(), true);
if (openApiUser == null)
{
openApiUser = await Context.Queryable<OpenApiUser>()
.Where(u => u.Id == Id)
.FirstAsync();
if (openApiUser != null)
{
//插入Cache
CacheStatic.Cache.Set(CacheConst.CACHE_OPENAPIUSER + openApiUser.Id.ToString(), openApiUser, true);
}
}
return openApiUser;
}
/// <inheritdoc />
[OperDesc("用户授权")]
public async Task GrantRoleAsync(OpenApiUserGrantPermissionInput input)
{
var openApiUser = await GetUsertByIdAsync(input.Id.Value);//获取用户信息
if (openApiUser != null)
{
openApiUser.PermissionCodeList = input.PermissionList;
await CheckInputAsync(openApiUser);
if (await Context.Updateable(openApiUser).IgnoreColumns(it =>
new
{
//忽略更新字段
it.Password,
it.LastLoginDevice,
it.LastLoginIp,
it.LastLoginTime,
it.LatestLoginDevice,
it.LatestLoginIp,
it.LatestLoginTime
}).ExecuteCommandAsync() > 0)//修改数据
DeleteUserFromCache(input.Id.Value);//从cache删除用户信息
}
}
/// <inheritdoc/>
public async Task<List<string>> OwnPermissionsAsync(BaseIdInput input)
{
var openApiUser = await GetUsertByIdAsync(input.Id);//获取用户信息
return openApiUser.PermissionCodeList;
}
/// <inheritdoc/>
public async Task<SqlSugarPagedList<OpenApiUser>> PageAsync(OpenApiUserPageInput input)
{
var query = Context.Queryable<OpenApiUser>()
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), u => u.Account.Contains(input.SearchKey));//根据关键字查询
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
query = query.OrderBy(it => it.SortCode);//排序
query = query.OrderBy(u => u.Id);//排序
var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页
return pageInfo;
}
/// <summary>
/// 检查输入参数
/// </summary>
/// <param name="openApiUser"></param>
private async Task CheckInputAsync(OpenApiUser openApiUser)
{
//判断账号重复,直接从cache拿
var account_Id = await GetIdByAccountAsync(openApiUser.Account);
if (account_Id > 0 && account_Id != openApiUser.Id)
throw Oops.Bah($"存在重复的账号:{openApiUser.Account}");
//如果手机号不是空
if (!string.IsNullOrEmpty(openApiUser.Phone))
{
if (!openApiUser.Phone.MatchPhoneNumber())//验证手机格式
throw Oops.Bah($"手机号码:{openApiUser.Phone} 格式错误");
openApiUser.Phone = DESCEncryption.Encrypt(openApiUser.Phone, DESCKeyConst.DESCKey);
}
//如果邮箱不是空
if (!string.IsNullOrEmpty(openApiUser.Email))
{
var ismatch = openApiUser.Email.MatchEmail();//验证邮箱格式
if (!ismatch)
throw Oops.Bah($"邮箱:{openApiUser.Email} 格式错误");
if (await IsAnyAsync(it => it.Email == openApiUser.Email && it.Id != openApiUser.Id))
throw Oops.Bah($"存在重复的邮箱:{openApiUser.Email}");
}
}
}

View File

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

View File

@@ -0,0 +1,18 @@
{
"RECORDS": [
{
"Id": 444657867911429,
"Category": "SYS_USER_HAS_ROLE",
"ObjectId": 212725263002001,
"TargetId": "212725263001001",
"ExtJson": null
},
{
"Id": 444657879060741,
"Category": "SYS_USER_HAS_ROLE",
"ObjectId": 201725263002001,
"TargetId": "212725263001002",
"ExtJson": null
}
]
}

View File

@@ -0,0 +1,564 @@
{
"RECORDS": [
{
"Id": "100",
"Title": "系统首页",
"Icon": "mdi-home-account",
"Name": "index",
"Component": "/index",
"Category": "SPA",
"Code": "system",
"ParentId": "0",
"SortCode": "1",
"TargetType": "SELF",
"IsDelete": "false"
},
{
"Id": "100001",
"Title": "权限管理",
"Icon": "mdi-account-hard-hat",
"Category": "MENU",
"Code": "system",
"ParentId": "0",
"SortCode": "4",
"TargetType": "None",
"IsDelete": "false",
"UpdateTime": "2023-02-26 00:55:23.977",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "100001001",
"Title": "用户管理",
"Icon": "mdi-account-edit",
"Name": "sysUser",
"Component": "/admin/user",
"Category": "MENU",
"Code": "system",
"ParentId": "100001",
"SortCode": "1",
"TargetType": "SELF",
"IsDelete": "false"
},
{
"Id": "100001002",
"Title": "角色管理",
"Icon": "mdi-account-hard-hat",
"Name": "sysRole",
"Component": "/admin/role",
"Category": "MENU",
"Code": "system",
"ParentId": "100001",
"SortCode": "2",
"TargetType": "SELF",
"IsDelete": "false"
},
{
"Id": "100001003",
"Title": "菜单管理",
"Icon": "mdi-menu",
"Name": "sysMenu",
"Component": "/admin/menu",
"Category": "MENU",
"Code": "system",
"ParentId": "100001",
"SortCode": "3",
"TargetType": "SELF",
"IsDelete": "false"
},
{
"Id": "100002002",
"Title": "访问日志",
"Icon": "mdi-account-switch-outline",
"Name": "sysVislog",
"Component": "/admin/vislog",
"Category": "MENU",
"Code": "system",
"ParentId": "100002",
"SortCode": "2",
"TargetType": "SELF",
"IsDelete": "false"
},
{
"Id": "100002003",
"Title": "操作日志",
"Icon": "mdi-database-search-outline",
"Name": "sysOplog",
"Component": "/admin/oplog",
"Category": "MENU",
"Code": "system",
"ParentId": "100002",
"SortCode": "3",
"TargetType": "SELF",
"IsDelete": "false"
},
{
"Id": "100002010",
"Title": "定时看板",
"Icon": "mdi-database-cog-outline",
"Name": "schedulePage",
"Component": "/schedulePage",
"Category": "MENU",
"Code": "system",
"ParentId": "100002",
"SortCode": "4",
"TargetType": "SELF",
"IsDelete": "false"
},
{
"Id": "100002",
"Title": "系统运维",
"Icon": "mdi-cogs",
"Category": "MENU",
"Code": "system",
"ParentId": "0",
"SortCode": "5",
"TargetType": "None",
"IsDelete": "false",
"UpdateTime": "2023-02-26 00:55:33.503",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "100002001",
"Title": "系统配置",
"Icon": "mdi-cog-transfer-outline",
"Name": "sysConfig",
"Component": "/admin/config",
"Category": "MENU",
"Code": "system",
"ParentId": "100002",
"SortCode": "1",
"TargetType": "SELF",
"IsDelete": "false"
},
{
"Id": "100003",
"Title": "第三方授权",
"Icon": "mdi-transit-transfer",
"Category": "MENU",
"Code": "system",
"ParentId": "0",
"SortCode": "6",
"TargetType": "None",
"IsDelete": "false",
"UpdateTime": "2023-02-26 00:55:29.094",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "100003001",
"Title": "令牌列表",
"Icon": "mdi-transit-transfer",
"Name": "sysOpenApiSession",
"Component": "/admin/openapisession",
"Category": "MENU",
"Code": "system",
"ParentId": "100003",
"SortCode": "1",
"TargetType": "SELF",
"IsDelete": "false"
},
{
"Id": "100003002",
"Title": "授权用户",
"Icon": "mdi-transit-transfer",
"Name": "sysOpenApiUser",
"Component": "/admin/openapiuser",
"Category": "MENU",
"Code": "system",
"ParentId": "100003",
"SortCode": "2",
"TargetType": "SELF",
"IsDelete": "false"
},
{
"Id": "100003003",
"Title": "接口文档",
"Icon": "mdi-cog-transfer-outline",
"Name": "swaggerUrl",
"Component": "/api/index.html",
"Category": "MENU",
"Code": "system",
"ParentId": "100003",
"SortCode": "3",
"TargetType": "BLANK",
"IsDelete": "false"
},
{
"Id": "100002004",
"Title": "会话管理",
"Icon": "mdi-transit-transfer",
"Name": "session",
"Component": "/admin/session",
"Category": "MENU",
"Code": "system",
"ParentId": "100002",
"SortCode": "4",
"TargetType": "SELF",
"IsDelete": "false"
},
{
"Id": "100001001001",
"Title": "添加",
"Name": "add",
"Category": "BUTTON",
"Code": "sysuseradd",
"ParentId": "100001001",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001004",
"Title": "单页管理",
"Icon": "mdi-menu",
"Name": "sysSpa",
"Component": "/admin/spa",
"Category": "MENU",
"Code": "system",
"ParentId": "100001",
"SortCode": "4",
"TargetType": "SELF",
"IsDelete": "false"
},
{
"Id": "100001001002",
"Title": "编辑",
"Name": "edit",
"Category": "BUTTON",
"Code": "sysuseredit",
"ParentId": "100001001",
"SortCode": "2",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001001003",
"Title": "删除",
"Name": "delete",
"Category": "BUTTON",
"Code": "sysuserdelete",
"ParentId": "100001001",
"SortCode": "3",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001002001",
"Title": "添加",
"Name": "add",
"Category": "BUTTON",
"Code": "sysroleadd",
"ParentId": "100001002",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001002002",
"Title": "编辑",
"Name": "edit",
"Category": "BUTTON",
"Code": "sysroleedit",
"ParentId": "100001002",
"SortCode": "2",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001002003",
"Title": "删除",
"Name": "delete",
"Category": "BUTTON",
"Code": "sysroledelete",
"ParentId": "100001002",
"SortCode": "3",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001003001",
"Title": "添加",
"Name": "add",
"Category": "BUTTON",
"Code": "sysmenuadd",
"ParentId": "100001003",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001003002",
"Title": "编辑",
"Name": "edit",
"Category": "BUTTON",
"Code": "sysmenuedit",
"ParentId": "100001003",
"SortCode": "2",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001003003",
"Title": "删除",
"Name": "delete",
"Category": "BUTTON",
"Code": "sysmenudelete",
"ParentId": "100001003",
"SortCode": "3",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001004001",
"Title": "添加",
"Name": "add",
"Category": "BUTTON",
"Code": "sysspaadd",
"ParentId": "100001004",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001004002",
"Title": "编辑",
"Name": "edit",
"Category": "BUTTON",
"Code": "sysspaedit",
"ParentId": "100001004",
"SortCode": "2",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001004003",
"Title": "删除",
"Name": "delete",
"Category": "BUTTON",
"Code": "sysspadelete",
"ParentId": "100001004",
"SortCode": "3",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100002001001",
"Title": "添加",
"Name": "add",
"Category": "BUTTON",
"Code": "sysconfigadd",
"ParentId": "100002001",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100002001002",
"Title": "编辑",
"Name": "edit",
"Category": "BUTTON",
"Code": "sysconfigedit",
"ParentId": "100002001",
"SortCode": "2",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100002001003",
"Title": "删除",
"Name": "delete",
"Category": "BUTTON",
"Code": "sysconfigdelete",
"ParentId": "100002001",
"SortCode": "3",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100003002001",
"Title": "添加",
"Name": "add",
"Category": "BUTTON",
"Code": "openapiuseradd",
"ParentId": "100003002",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100003002002",
"Title": "编辑",
"Name": "edit",
"Category": "BUTTON",
"Code": "openapiuseredit",
"ParentId": "100003002",
"SortCode": "2",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100003002003",
"Title": "删除",
"Name": "delete",
"Category": "BUTTON",
"Code": "openapiuserdelete",
"ParentId": "100003002",
"SortCode": "3",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001001004",
"Title": "重置密码",
"Name": "resetpassword",
"Category": "BUTTON",
"Code": "sysuserresetpassword",
"ParentId": "100001001",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001001005",
"Title": "角色授权",
"Name": "perrole",
"Category": "BUTTON",
"Code": "sysuserperrole",
"ParentId": "100001001",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001002004",
"Title": "资源授权",
"Name": "perresuorce",
"Category": "BUTTON",
"Code": "sysroleperresuorce",
"ParentId": "100001002",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100001002005",
"Title": "用户授权",
"Name": "peruser",
"Category": "BUTTON",
"Code": "sysroleperuser",
"ParentId": "100001002",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100002004004",
"Title": "会话强退",
"Name": "exit",
"Category": "BUTTON",
"Code": "syssessionexit",
"ParentId": "100002004",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100002004005",
"Title": "令牌删除",
"Name": "verificatdelete",
"Category": "BUTTON",
"Code": "sysverificatdelete",
"ParentId": "100002004",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100003001004",
"Title": "会话强退",
"Name": "exit",
"Category": "BUTTON",
"Code": "openapisessionexit",
"ParentId": "100003001",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100003001005",
"Title": "令牌删除",
"Name": "verificatdelete",
"Category": "BUTTON",
"Code": "openapiverificatdelete",
"ParentId": "100003001",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100002002003",
"Title": "清空",
"Name": "clear",
"Category": "BUTTON",
"Code": "sysoplogclear",
"ParentId": "100002002",
"SortCode": "3",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100002003003",
"Title": "清空",
"Name": "clear",
"Category": "BUTTON",
"Code": "sysvislogclear",
"ParentId": "100002003",
"SortCode": "3",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100003002004",
"Title": "修改密码",
"Name": "editpassword",
"Category": "BUTTON",
"Code": "openapiusereditpassword",
"ParentId": "100003002",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "100003002005",
"Title": "授权Api",
"Name": "per",
"Category": "BUTTON",
"Code": "openapiuserper",
"ParentId": "100003002",
"SortCode": "1",
"TargetType": "None",
"IsDelete": "false"
},
{
"Id": "391545543004421",
"Title": "个人中心",
"Icon": "mdi-home-account",
"Component": "/usercenter",
"Category": "SPA",
"Code": "391545542885637",
"ParentId": "0",
"SortCode": "2",
"TargetType": "SELF",
"CreateTime": "2023-03-02 19:42:55.6049703",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": "false",
"UpdateTime": "2023-03-02 19:46:13.3919053",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
}
]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,26 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 即时通讯集线器
/// </summary>
public interface ISysHub
{
/// <summary>
/// 退出登录
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task Logout(object context);
}

View File

@@ -0,0 +1,118 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.InstantMessaging;
using Furion.Logging.Extensions;
using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// <inheritdoc cref="ISysHub"/>
/// </summary>
[MapHub(HubConst.SysHubUrl)]
public class SysHub : Hub<ISysHub>
{
readonly ILogger<ISysHub> _logger;
/// <inheritdoc cref="ISysHub"/>
public SysHub(ILogger<ISysHub> logger)
{
_logger = logger;
}
/// <summary>
/// 分隔符
/// </summary>
public const string SYS_TrackingCircuitHandlerid = "SYS_TrackingCircuitHandlerid";
/// <summary>
/// 连接
/// </summary>
/// <returns></returns>
public override async Task OnConnectedAsync()
{
var feature = Context.Features.Get<IHttpContextFeature>();
var VerificatId = feature.HttpContext.Request.Headers[ClaimConst.VerificatId].FirstOrDefault().ToLong();
var userIdentifier = Context.UserIdentifier;//自定义的Id
await UpdateVerificatAsync(userIdentifier, verificat: VerificatId);//更新cache
await base.OnConnectedAsync();
}
/// <summary>
/// 断开连接
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
public override async Task OnDisconnectedAsync(Exception exception)
{
var userIdentifier = Context.UserIdentifier;//自定义的Id
await UpdateVerificatAsync(userIdentifier, false);//更新cache
await base.OnDisconnectedAsync(exception);
}
#region
/// <summary>
/// 更新cache
/// </summary>
/// <param name="userIdentifier">用户id</param>
/// <param name="verificat">上线时的验证id</param>
/// <param name="isConnect">是否是上线</param>
private async Task UpdateVerificatAsync(string userIdentifier, bool isConnect = true, long verificat = 0)
{
var userId = userIdentifier.Split(SYS_TrackingCircuitHandlerid)[0].ToLong();//分割取第一个
if (userId > 0)
{
var _verificatService = App.GetService<IVerificatService>();
//获取cache当前用户的verificat信息列表
List<VerificatInfo> verificatInfos = await _verificatService.GetVerificatIdAsync(userId);
if (verificatInfos != null)
{
if (isConnect)
{
//获取cache中当前verificat
var verificatInfo = verificatInfos.Where(it => it.Id == verificat).FirstOrDefault();
if (verificatInfo != null)
{
verificatInfo.ClientIds.Add(userIdentifier);//添加到客户端列表
await _verificatService.SetVerificatIdAsync(userId, verificatInfos);
}
}
else
{
//获取当前客户端ID所在的verificat信息
var verificatInfo = verificatInfos.Where(it => it.ClientIds.Contains(userIdentifier)).FirstOrDefault();
if (verificatInfo != null)
{
verificatInfo.ClientIds.RemoveWhere(it => it == userIdentifier);//从客户端列表删除
await _verificatService.SetVerificatIdAsync(userId, verificatInfos);
}
}
}
}
else
{
if (isConnect)
_logger.LogWarning($"未认证SignalR ID{userIdentifier} 登录");
else
_logger.LogWarning($"未认证SignalR ID{userIdentifier} 注销");
}
}
#endregion
}

View File

@@ -0,0 +1,40 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.SignalR;
using ThingsGateway.Admin.Core;
using Yitter.IdGenerator;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 用户ID提供器
/// </summary>
public class UserIdProvider : IUserIdProvider
{
/// <inheritdoc/>
public string GetUserId(HubConnectionContext connection)
{
var feature = connection.Features.Get<IHttpContextFeature>();
var UserId = feature.HttpContext.Request.Headers[ClaimConst.UserId].FirstOrDefault()?.ToLong();
if (UserId > 0)
{
return $"{UserId}{SysHub.SYS_TrackingCircuitHandlerid}{YitIdHelper.NextId()}";//返回用户ID
}
return connection.ConnectionId;
}
}

View File

@@ -0,0 +1,43 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Microsoft.Extensions.DependencyInjection;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// AppStartup启动类
/// </summary>
public class Startup : AppStartup
{
/// <summary>
/// 配置
/// </summary>
public void ConfigureServices(IServiceCollection services)
{
// 任务调度
services.AddSchedule(options =>
{
options.AddPersistence<JobPersistence>();
});
//事件总线
services.AddEventBus();
}
}

View File

@@ -0,0 +1,55 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Furion.EventBus;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 认证模块事件总线
/// </summary>
public class AuthEventSubscriber : IEventSubscriber, ISingleton
{
/// <summary>
/// 登录事件
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
[EventSubscribe(EventSubscriberConst.Login)]
public async Task Login(EventHandlerExecutingContext context)
{
var loginEvent = (LoginEvent)context.Source.Payload;//获取参数
var sysUser = loginEvent.SysUser;
var db = DbContext.Db.CopyNew();
#region ,
db.Tracking(sysUser);//创建跟踪,只更新修改字段
sysUser.LastLoginDevice = sysUser.LatestLoginDevice;
sysUser.LastLoginIp = sysUser.LatestLoginIp;
sysUser.LastLoginTime = sysUser.LatestLoginTime;
sysUser.LatestLoginDevice = loginEvent.Device.ToString();
sysUser.LatestLoginIp = loginEvent.Ip;
sysUser.LatestLoginTime = loginEvent.DateTime;
#endregion ,
//更新用户信息
if (await db.UpdateableWithAttr(sysUser).ExecuteCommandAsync() > 0)
{
CacheStatic.Cache.Set(CacheConst.CACHE_SYSUSER + sysUser.Id, sysUser, false); //更新Cache信息
}
}
}

View File

@@ -0,0 +1,59 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Furion.EventBus;
using Microsoft.Extensions.DependencyInjection;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 用户模块事件总线
/// </summary>
public class UserEventSubscriber : IEventSubscriber, ISingleton
{
private readonly IServiceProvider _services;
/// <summary>
/// <inheritdoc cref="UserEventSubscriber"/>
/// </summary>
/// <param name="services"></param>
public UserEventSubscriber(IServiceProvider services)
{
_services = services;
}
/// <summary>
/// 根据角色ID列表清除用户缓存
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
[EventSubscribe(EventSubscriberConst.ClearUserCache)]
public async Task DeleteUserCacheByRoleIds(EventHandlerExecutingContext context)
{
var roleIds = (List<long>)context.Source.Payload;//获取角色ID
// 创建新的作用域
using var scope = _services.CreateScope();
// 解析角色服务
var relationService = scope.ServiceProvider.GetRequiredService<IRelationService>();
//获取用户和角色关系
var relations = await relationService.GetRelationListByTargetIdListAndCategoryAsync(roleIds.Select(it => it.ToString()).ToList(), CateGoryConst.Relation_SYS_USER_HAS_ROLE);
if (relations.Count > 0)
{
var userIds = relations.Select(it => it.ObjectId).ToArray();//用户ID列表
// 解析用户服务
var userService = scope.ServiceProvider.GetRequiredService<ISysUserService>();
//从缓存中删除
userService.DeleteUserFromCache(userIds);
}
}
}

View File

@@ -0,0 +1,240 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.DataEncryption;
using Furion.EventBus;
using Furion.FriendlyException;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using ThingsGateway.Admin.Core;
using Yitter.IdGenerator;
namespace ThingsGateway.Admin.Application;
/// <inheritdoc cref="IAuthService"/>
public class AuthService : IAuthService
{
private readonly IConfigService _configService;
private readonly IEventPublisher _eventPublisher;
private readonly INoticeService _noticeService;
private readonly ISysUserService _userService;
private readonly IVerificatService _verificatService;
/// <inheritdoc cref="IAuthService"/>
public AuthService(
IEventPublisher eventPublisher,
ISysUserService userService,
IConfigService configService,
IVerificatService verificatService,
INoticeService noticeService
)
{
_eventPublisher = eventPublisher;
_userService = userService;
_configService = configService;
_verificatService = verificatService;
_noticeService = noticeService;
}
/// <inheritdoc/>
public ValidCodeOutput GetCaptchaInfo()
{
//生成验证码
var captchInfo = new Random().Next(1111, 9999).ToString();
//生成请求号并将验证码放入cache
var reqNo = YitIdHelper.NextId();
//插入cache
CacheStatic.Cache.Set(CacheConst.LOGIN_CAPTCHA + reqNo, captchInfo, TimeSpan.FromMinutes(1), false);
//返回验证码和请求号
return new ValidCodeOutput { CodeValue = captchInfo, ValidCodeReqNo = reqNo };
}
/// <inheritdoc/>
public Task<SysUser> GetLoginUserAsync()
{
return _userService.GetUserByIdAsync(UserManager.UserId);
}
/// <inheritdoc/>
public async Task<LoginOutput> LoginAsync(LoginInput input)
{
//判断是否有验证码
var sysBase = await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_CAPTCHA_OPEN);
if (sysBase != null)//如果有这个配置项
{
if (sysBase.ConfigValue.ToBoolean())//如果需要验证码
{
//如果没填验证码,提示验证码不能为空
if (input.ValidCode.IsNullOrEmpty() || input.ValidCodeReqNo == 0) throw Oops.Bah("验证码不能为空").StatusCode(410);
ValidValidCode(input.ValidCode, input.ValidCodeReqNo);//校验验证码
}
}
var password = DESCEncryption.Decrypt(input.Password, DESCKeyConst.DESCKey); // 解密
var userInfo = await _userService.GetUserByAccountAsync(input.Account);//获取用户信息
if (userInfo == null) throw Oops.Bah("用户不存在");//用户不存在
if (userInfo.Password != password) throw Oops.Bah("账号密码错误");//账号密码错误
return await LoginAsync(userInfo, input.Device);
}
/// <inheritdoc/>
public async Task LogoutAsync()
{
//获取用户信息
var userinfo = await _userService.GetUserByAccountAsync(UserManager.UserAccount);
if (userinfo != null)
{
LoginEvent loginEvent = new()
{
Ip = App.HttpContext.GetRemoteIpAddressToIPv4(),
SysUser = userinfo,
VerificatId = UserManager.VerificatId.ToLong(),
};
await RemoveVerificatAsync(loginEvent);//移除验证Id
}
}
/// <summary>
/// 校验验证码方法
/// </summary>
/// <param name="validCode">验证码</param>
/// <param name="validCodeReqNo">请求号</param>
/// <param name="isDelete">是否从Cache删除</param>
private static void ValidValidCode(string validCode, long validCodeReqNo, bool isDelete = true)
{
var code = CacheStatic.Cache.Get<string>(CacheConst.LOGIN_CAPTCHA + validCodeReqNo, false);//从cache拿数据
if (isDelete) CacheStatic.Cache.Remove(CacheConst.LOGIN_CAPTCHA + validCodeReqNo);//删除验证码
if (code != null)//如果有
{
//验证码如果不匹配直接抛错误,这里忽略大小写
if (validCode.ToLower() != code.ToLower()) throw Oops.Bah("验证码错误");
}
else
{
throw Oops.Bah("验证码不能为空");//抛出验证码不能为空
}
}
/// <summary>
/// 执行B端登录
/// </summary>
/// <param name="sysUser">用户信息</param>
/// <param name="device">登录设备</param>
/// <returns></returns>
private async Task<LoginOutput> LoginAsync(SysUser sysUser, AuthDeviceTypeEnum device)
{
if (sysUser.UserEnable == false) throw Oops.Bah("账号已停用");//账号已停用
var sysBase = await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_VERIFICAT_EXPIRES);
var sessionid = YitIdHelper.NextId();
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimConst.VerificatId, sessionid.ToString()));
identity.AddClaim(new Claim(ClaimConst.UserId, sysUser.Id.ToString()));
identity.AddClaim(new Claim(ClaimConst.Account, sysUser.Account));
identity.AddClaim(new Claim(ClaimConst.IsSuperAdmin, sysUser.RoleCodeList.Contains(RoleConst.SuperAdmin).ToString()));
identity.AddClaim(new Claim(ClaimConst.IsOpenApi, false.ToString()));
var config = sysBase.ConfigValue.ToInt(2880);
var diffTime = SysDateTimeExtensions.CurrentDateTime.AddMinutes(config);
await App.HttpContext.SignInAsync(new ClaimsPrincipal(identity), new AuthenticationProperties()
{
IsPersistent = true,
ExpiresUtc = diffTime,
});
//登录事件参数
var loginEvent = new LoginEvent
{
Ip = App.HttpContext.GetRemoteIpAddressToIPv4(),
Device = device,
Expire = config,
SysUser = sysUser,
VerificatId = sessionid,
};
await SetVerificatAsync(loginEvent);//写入verificat
await _eventPublisher.PublishAsync(EventSubscriberConst.Login, loginEvent); //发布登录事件总线
return new LoginOutput { VerificatId = sessionid, Account = sysUser.Account };
}
private async Task RemoveVerificatAsync(LoginEvent loginEvent)
{
//获取verificat列表
List<VerificatInfo> verificatInfos = await _verificatService.GetVerificatIdAsync(loginEvent.SysUser.Id);
if (verificatInfos != null)
{
//获取当前用户的verificat
var verificat = verificatInfos.Where(it => it.Id == loginEvent.VerificatId).FirstOrDefault();
if (verificat != null)
verificatInfos.Remove(verificat);
//更新verificat列表
await _verificatService.SetVerificatIdAsync(loginEvent.SysUser.Id, verificatInfos);
}
await App.HttpContext?.SignOutAsync();
App.HttpContext?.SignoutToSwagger();
}
/// <summary>
/// 写入验证信息到缓存
/// </summary>
/// <param name="loginEvent"></param>
/// <returns></returns>
private async Task SetVerificatAsync(LoginEvent loginEvent)
{
//获取verificat列表
List<VerificatInfo> verificatInfos = await _verificatService.GetVerificatIdAsync(loginEvent.SysUser.Id);
var verificatTimeout = loginEvent.DateTime.AddMinutes(loginEvent.Expire);
//生成verificat信息
var verificatInfo = new VerificatInfo
{
Device = loginEvent.Device.ToString(),
Expire = loginEvent.Expire,
VerificatTimeout = verificatTimeout,
Id = loginEvent.VerificatId,
UserId = loginEvent.SysUser.Id,
};
if (verificatInfos != null)
{
bool isSingle = false;//默认不开启单用户登录
var singleConfig = await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SINGLE_OPEN);//获取系统单用户登录选项
if (singleConfig != null) isSingle = singleConfig.ConfigValue.ToBoolean();//如果配置不为空则设置单用户登录选项为系统配置的值
if (isSingle)//判断是否单用户登录
{
await _noticeService.LogoutAsync(loginEvent.SysUser.Id, verificatInfos.Where(it => it.Device == loginEvent.Device.ToString()).ToList(), "该账号已在别处登录!");//通知其他用户下线
verificatInfos = verificatInfos.Where(it => it.Device != loginEvent.Device.ToString()).ToList();//去掉当前登录类型
verificatInfos.Add(verificatInfo);//添加到列表
}
else
{
verificatInfos.Add(verificatInfo);
}
}
else
{
verificatInfos = new List<VerificatInfo> { verificatInfo };//直接就一个
}
//添加到verificat列表
await _verificatService.SetVerificatIdAsync(loginEvent.SysUser.Id, verificatInfos);
}
}

View File

@@ -0,0 +1,78 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 登录输入参数
/// </summary>
public class LoginInput : ValidCodeInput
{
/// <summary>
/// 账号
///</summary>
[Required(ErrorMessage = "账号不能为空"), MinLength(3, ErrorMessage = "账号不能少于4个字符")]
public string Account { get; set; }
/// <summary>
/// 设备类型默认PC
/// </summary>
/// <example>0</example>
public AuthDeviceTypeEnum Device { get; set; } = AuthDeviceTypeEnum.PC;
/// <summary>
/// 密码
///</summary>
[Required(ErrorMessage = "密码不能为空"), MinLength(3, ErrorMessage = "密码不能少于3个字符")]
public string Password { get; set; }
}
/// <summary>
/// 验证码输入
/// </summary>
public class ValidCodeInput
{
/// <summary>
/// 验证码
/// </summary>
public string ValidCode { get; set; }
/// <summary>
/// 请求号
/// </summary>
public long ValidCodeReqNo { get; set; }
}
/// <summary>
/// 登录设备类型枚举
/// </summary>
public enum AuthDeviceTypeEnum
{
/// <summary>
/// PC端
/// </summary>
[Description("PC端")]
PC,
/// <summary>
/// 移动端
/// </summary>
[Description("移动端")]
APP,
/// <summary>
/// Api
/// </summary>
[Description("Api")]
Api,
}

View File

@@ -1,48 +1,51 @@
//------------------------------------------------------------------------------
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 周统计输出
/// 登录返回参数
/// </summary>
public class RpcLogDayStatisticsOutput
public class BaseLoginOutput
{
/// <summary>
/// 日期
/// 账号
/// </summary>
public string Date { get; set; }
public string Account { get; set; }
/// <summary>
/// 失败次数
/// 验证ID
/// </summary>
public int FailCount { get; set; }
/// <summary>
/// 成功次数
/// </summary>
public int SuccessCount { get; set; }
public long VerificatId { get; set; }
}
/// <summary>
/// 周统计输出
/// 验证码值返回
/// </summary>
public class BackendLogDayStatisticsOutput
public class ValidCodeOutput
{
/// <summary>
/// 日期
/// 验证码值
/// </summary>
public string Date { get; set; }
public string CodeValue { get; set; }
public int DebugCount { get; set; }
public int ErrorCount { get; set; }
public int InfoCount { get; set; }
public int WarningCount { get; set; }
/// <summary>
/// 验证码请求号
/// </summary>
public long ValidCodeReqNo { get; set; }
}
/// <summary>
/// 登录返回参数
/// </summary>
public class LoginOutput : BaseLoginOutput
{
}

View File

@@ -0,0 +1,51 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 登录事件参数
/// </summary>
public class LoginEvent
{
/// <summary>
/// 时间
/// </summary>
public DateTime DateTime = SysDateTimeExtensions.CurrentDateTime;
/// <summary>
/// 登录设备
/// </summary>
public AuthDeviceTypeEnum Device { get; set; }
/// <summary>
/// 过期时间(分)
/// </summary>
public int Expire { get; set; }
/// <summary>
/// Ip地址
/// </summary>
public string Ip { get; set; }
/// <summary>
/// 用户信息
/// </summary>
public SysUser SysUser { get; set; }
/// <summary>
/// 验证Id
/// </summary>
public long VerificatId { get; set; }
}

View File

@@ -0,0 +1,46 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 权限校验服务
/// </summary>
public interface IAuthService : ITransient
{
/// <summary>
/// 生成验证码
/// </summary>
/// <returns></returns>
ValidCodeOutput GetCaptchaInfo();
/// <summary>
/// 获取登录用户信息
/// </summary>
/// <returns></returns>
Task<SysUser> GetLoginUserAsync();
/// <summary>
/// 登录
/// </summary>
Task<LoginOutput> LoginAsync(LoginInput input);
/// <summary>
/// 退出登录
/// </summary>
/// <returns></returns>
Task LogoutAsync();
}

View File

@@ -0,0 +1,166 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Furion.FriendlyException;
using Mapster;
using ThingsGateway.Admin.Core;
using ThingsGateway.Admin.Core.JsonExtensions;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// <inheritdoc cref="IButtonService"/>
/// </summary>
[Injection(Proxy = typeof(OperDispatchProxy))]
public class ButtonService : DbRepository<SysResource>, IButtonService
{
private readonly IRelationService _relationService;
private readonly IResourceService _resourceService;
/// <inheritdoc cref="IButtonService"/>
public ButtonService(
IResourceService resourceService,
IRelationService relationService
)
{
_resourceService = resourceService;
_relationService = relationService;
}
/// <inheritdoc />
public async Task AddAsync(ButtonAddInput input)
{
await CheckInputAsync(input);//检查参数
var sysResource = input.Adapt<SysResource>();//实体转换
if (await InsertAsync(sysResource))//插入数据
_resourceService.RefreshCache(ResourceCategoryEnum.BUTTON);//刷新缓存
}
/// <inheritdoc />
[OperDesc("删除按钮")]
public async Task DeleteAsync(params long[] input)
{
//获取所有ID
var ids = input.ToList();
//获取所有按钮集合
var buttonList = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.BUTTON);
#region
//获取所有菜单集合
var menuList = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.MENU);
//获取按钮的父菜单id集合
var parentIds = buttonList.Where(it => ids.Contains(it.Id)).Select(it => it.ParentId.ToString()).ToList();
//获取关系表分类为SYS_ROLE_HAS_RESOURCE数据
var roleResources = await _relationService.GetRelationByCategoryAsync(CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE);
//获取相关关系表数据
var relationList = roleResources
.Where(it => parentIds.Contains(it.TargetId))//目标ID是父ID中
.Where(it => it.ExtJson != null).ToList();//扩展信息不为空
//遍历关系表
relationList.ForEach(it =>
{
var relationRoleResuorce = it.ExtJson.ToJsonWithT<RelationRoleResuorce>();//拓展信息转实体
var buttonInfo = relationRoleResuorce.ButtonInfo;//获取按钮信息
if (buttonInfo.Count > 0)
{
var diffArr = buttonInfo.Where(it => !buttonInfo.Contains(it)).ToList(); //找出不同的元素(即交集的补集)
relationRoleResuorce.ButtonInfo = diffArr;//重新赋值按钮信息
it.ExtJson = relationRoleResuorce.ToJsonString();//重新赋值拓展信息
}
});
#endregion
//事务
var result = await itenant.UseTranAsync(async () =>
{
await DeleteByIdsAsync(ids.Cast<object>().ToArray());//删除按钮
if (relationList.Count > 0)
{
await Context.Updateable(relationList).UpdateColumns(it => it.ExtJson).ExecuteCommandAsync();//修改拓展信息
}
});
if (result.IsSuccess)//如果成功了
{
_resourceService.RefreshCache(ResourceCategoryEnum.BUTTON);//资源表按钮刷新缓存
}
else
{
throw Oops.Oh(result.ErrorMessage);
}
}
/// <inheritdoc />
[OperDesc("编辑按钮")]
public async Task EditAsync(ButtonEditInput input)
{
await CheckInputAsync(input);//检查参数
var sysResource = input.Adapt<SysResource>();//实体转换
//事务
var result = await itenant.UseTranAsync(async () =>
{
await UpdateAsync(sysResource); //更新按钮
});
if (result.IsSuccess)//如果成功了
{
_resourceService.RefreshCache(ResourceCategoryEnum.BUTTON);//资源表按钮刷新缓存
}
else
{
throw Oops.Oh(result.ErrorMessage);
}
}
/// <inheritdoc/>
public async Task<SqlSugarPagedList<SysResource>> PageAsync(ButtonPageInput input)
{
var query = Context.Queryable<SysResource>()
.Where(it => it.ParentId == input.ParentId && it.Category == ResourceCategoryEnum.BUTTON)
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Title.Contains(input.SearchKey) || it.Component.Contains(input.SearchKey));//根据关键字查询
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
query = query.OrderBy(it => it.SortCode);//排序
var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页
return pageInfo;
}
#region
/// <summary>
/// 检查输入参数
/// </summary>
/// <param name="sysResource"></param>
private async Task CheckInputAsync(SysResource sysResource)
{
//获取所有按钮和菜单
var buttonList = await _resourceService.GetListByCategorysAsync(new List<ResourceCategoryEnum> { ResourceCategoryEnum.BUTTON, ResourceCategoryEnum.MENU });
//判断code是否重复
if (buttonList.Any(it => it.Code == sysResource.Code && it.Id != sysResource.Id))
throw Oops.Bah($"存在重复的按钮编码:{sysResource.Code}");
//判断菜单是否存在
if (!buttonList.Any(it => it.Id == sysResource.ParentId))
throw Oops.Bah($"不存在的父级菜单:{sysResource.ParentId}");
sysResource.Category = ResourceCategoryEnum.BUTTON;//设置分类为按钮
}
#endregion
}

View File

@@ -0,0 +1,63 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel.DataAnnotations;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 添加按钮参数
/// </summary>
public class ButtonAddInput : SysResource
{
/// <summary>
/// 编码
/// </summary>
[Required(ErrorMessage = "Code不能为空")]
public override string Code { get; set; }
/// <summary>
/// 父ID
/// </summary>
[Required(ErrorMessage = "ParentId不能为空")]
public override long ParentId { get; set; }
/// <summary>
/// 标题
/// </summary>
[Required(ErrorMessage = "Title不能为空")]
public override string Title { get; set; }
}
/// <summary>
/// 按钮分页
/// </summary>
public class ButtonPageInput : BasePageInput
{
/// <summary>
/// 父ID
/// </summary>
[Required(ErrorMessage = "ParentId不能为空")]
public long? ParentId { get; set; }
}
/// <summary>
/// 按钮编辑
/// </summary>
public class ButtonEditInput : ButtonAddInput
{
/// <summary>
/// ID
/// </summary>
[MinValue(1, ErrorMessage = "Id不能为空")]
public override long Id { get; set; }
}

View File

@@ -0,0 +1,53 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 权限按钮服务
/// </summary>
public interface IButtonService : ITransient
{
/// <summary>
/// 添加按钮
/// </summary>
/// <param name="input">添加参数</param>
/// <returns></returns>
Task AddAsync(ButtonAddInput input);
/// <summary>
/// 删除按钮
/// </summary>
/// <param name="input">删除参数</param>
/// <returns></returns>
Task DeleteAsync(params long[] input);
/// <summary>
/// 编辑按钮
/// </summary>
/// <param name="input">编辑参数</param>
/// <returns></returns>
Task EditAsync(ButtonEditInput input);
/// <summary>
/// 按钮分页查询
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>按钮分页列表</returns>
Task<SqlSugarPagedList<SysResource>> PageAsync(ButtonPageInput input);
}

View File

@@ -0,0 +1,132 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Furion.FriendlyException;
using Mapster;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <inheritdoc cref="IConfigService"/>
[Injection(Proxy = typeof(OperDispatchProxy))]
public class ConfigService : DbRepository<SysConfig>, IConfigService
{
/// <inheritdoc/>
[OperDesc("编辑网关系统配置")]
public async Task EditBatchAsync(List<SysConfig> sysConfigs)
{
if (await UpdateRangeAsync(sysConfigs))
RefreshCache(sysConfigs.FirstOrDefault()?.Category);//刷新缓存
}
/// <inheritdoc/>
[OperDesc("添加配置项")]
public async Task AddAsync(ConfigAddInput input)
{
await CheckInputAsync(input);//检查
var sysConfig = input.Adapt<SysConfig>();//实体转换
if (await InsertAsync(sysConfig))//插入数据)
RefreshCache(input.Category);//刷新缓存
}
/// <inheritdoc/>
[OperDesc("删除配置项")]
public async Task DeleteAsync(params long[] input)
{
await AsDeleteable().Where(it => input.Contains(it.Id)).ExecuteCommandAsync();
RefreshCache(ConfigConst.SYS_CONFIGOTHER);//刷新缓存
}
/// <inheritdoc/>
[OperDesc("编辑配置项")]
public async Task EditAsync(ConfigEditInput input)
{
await CheckInputAsync(input);
var sysConfig = input.Adapt<SysConfig>();//实体转换
if (await UpdateAsync(sysConfig))//更新数据
RefreshCache(input.Category);//刷新缓存
}
/// <inheritdoc/>
public async Task<SysConfig> GetByConfigKeyAsync(string category, string configKey)
{
var configList = await GetListByCategoryAsync(category);//获取系统配置列表
return configList.FirstOrDefault(it => it.ConfigKey == configKey);//根据configkey获取对应值
}
/// <inheritdoc/>
public async Task<List<SysConfig>> GetListByCategoryAsync(string category)
{
//先从Cache拿需要获取新的对象避免操作导致缓存中对象改变
var configList = CacheStatic.Cache.Get<List<SysConfig>>(CacheConst.SYS_CONFIGCATEGORY + category, true);
if (configList == null)
{
//cache没有再去数据可拿
configList = await Context.Queryable<SysConfig>().Where(it => it.Category == category).OrderBy(it => it.SortCode).ToListAsync();//获取系统配置列表
if (configList.Count > 0)
{
CacheStatic.Cache.Set(CacheConst.SYS_CONFIGCATEGORY + category, configList, true);//如果不为空,插入cache
}
}
return configList;
}
/// <inheritdoc/>
public async Task<SqlSugarPagedList<SysConfig>> PageAsync(ConfigPageInput input)
{
var query = Context.Queryable<SysConfig>()
.Where(it => it.Category == ConfigConst.SYS_CONFIGOTHER)//自定义配置
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.ConfigKey.Contains(input.SearchKey) || it.ConfigKey.Contains(input.SearchKey));
//根据关键字查询
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
query = query.OrderBy(it => it.SortCode);//排序
var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页
return pageInfo;
}
#region
/// <summary>
/// 检查输入参数,并设置分类为自定义分类
/// </summary>
/// <param name="sysConfig"></param>
private async Task CheckInputAsync(SysConfig sysConfig)
{
var configs = await GetListByCategoryAsync(sysConfig.Category);//获取全部字典
var hasSameKey = configs.Any(it => it.ConfigKey == sysConfig.ConfigKey && it.Id != sysConfig.Id);
//判断是否从存在重复字典名
if (hasSameKey)
{
throw Oops.Bah($"存在重复的配置键:{sysConfig.ConfigKey}");
}
sysConfig.Category = ConfigConst.SYS_CONFIGOTHER;
}
/// <summary>
/// 刷新缓存
/// </summary>
/// <param name="category">分类</param>
/// <returns></returns>
private void RefreshCache(string category)
{
CacheStatic.Cache.Remove(CacheConst.SYS_CONFIGCATEGORY + category);//cache删除
}
#endregion
}

View File

@@ -0,0 +1,67 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 添加配置参数
/// </summary>
public class ConfigAddInput : SysConfig
{
/// <summary>
/// 分类
/// </summary>
[Required(ErrorMessage = "Category不能为空")]
public override string Category { get; set; } = ConfigConst.SYS_CONFIGOTHER;
/// <summary>
/// 配置键
/// </summary>
[Required(ErrorMessage = "configKey不能为空")]
public override string ConfigKey { get; set; }
/// <summary>
/// 配置值
/// </summary>
[Required(ErrorMessage = "ConfigValue不能为空")]
public override string ConfigValue { get; set; }
}
/// <summary>
/// 编辑配置参数
/// </summary>
public class ConfigEditInput : ConfigAddInput
{
/// <summary>
/// ID
/// </summary>
[MinValue(1, ErrorMessage = "Id不能为空")]
public override long Id { get; set; }
}
/// <summary>
/// 配置分页参数
/// </summary>
public class ConfigPageInput : BasePageInput
{
/// <summary>
/// 分类
/// </summary>
[Description("分类")]
public string Category { get; set; }
}

View File

@@ -0,0 +1,72 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 系统配置服务
/// </summary>
public interface IConfigService : ITransient
{
/// <summary>
/// 批量编辑系统配置
/// </summary>
/// <param name="configs">配置列表</param>
/// <returns></returns>
Task EditBatchAsync(List<SysConfig> configs);
/// <summary>
/// 新增自定义配置
/// </summary>
/// <param name="input">新增参数</param>
/// <returns></returns>
Task AddAsync(ConfigAddInput input);
/// <summary>
/// 删除自定义配置
/// </summary>
/// <param name="input">删除</param>
/// <returns></returns>
Task DeleteAsync(params long[] input);
/// <summary>
/// 修改自定义配置
/// </summary>
/// <param name="input">修改参数</param>
/// <returns></returns>
Task EditAsync(ConfigEditInput input);
/// <summary>
/// 根据分类和配置键获配置
/// </summary>
/// <param name="category">分类</param>
/// <param name="configKey">配置键</param>
/// <returns>配置信息</returns>
Task<SysConfig> GetByConfigKeyAsync(string category, string configKey);
/// <summary>
/// 根据分类获取配置列表
/// </summary>
/// <param name="category">分类名称</param>
/// <returns>配置列表</returns>
Task<List<SysConfig>> GetListByCategoryAsync(string category);
/// <summary>
/// 分页查询自定义配置
/// </summary>
/// <param name="input">查询参数</param>
/// <returns>其他配置列表</returns>
Task<SqlSugarPagedList<SysConfig>> PageAsync(ConfigPageInput input);
}

View File

@@ -0,0 +1,38 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.FriendlyException;
using Microsoft.AspNetCore.Components.Forms;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// <inheritdoc cref="IFileService"/>
/// </summary>
public class FileService : IFileService
{
/// <inheritdoc/>
public void ImportVerification(IBrowserFile file, int maxSzie = 300, string[] allowTypes = null)
{
if (file == null) throw Oops.Bah("文件不能为空");
if (file.Size > maxSzie * 1024 * 1024) throw Oops.Bah($"文件大小不允许超过{maxSzie}M");
var fileSuffix = Path.GetExtension(file.Name).ToLower().Split(".")[1]; // 文件后缀
string[] allowTypeS = allowTypes ?? new string[] { "xlsx" };//允许上传的文件类型
if (!allowTypeS.Contains(fileSuffix)) throw Oops.Bah(errorMessage: "文件格式错误");
}
}

View File

@@ -1,32 +1,35 @@
//------------------------------------------------------------------------------
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Microsoft.AspNetCore.Components.Forms;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 插件添加DTO
/// 文件管理服务
/// </summary>
public class PluginAddInput
public interface IFileService : ITransient
{
/// <summary>
/// 主程序集
/// </summary>
[Required]
public IBrowserFile MainFile { get; set; }
/// <summary>
/// 附属程序集
/// 验证上传文件
/// </summary>
public List<IBrowserFile> OtherFiles { get; set; }
/// <param name="file">文件</param>
/// <param name="maxSzie">最大体积(M)</param>
/// <param name="allowTypes">允许上传类型</param>
void ImportVerification(IBrowserFile file, int maxSzie = 30, string[] allowTypes = null);
}

View File

@@ -0,0 +1,93 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel.DataAnnotations;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 添加菜单参数
/// </summary>
public class MenuAddInput : SysResource, IValidatableObject
{
/// <summary>
/// 路径
/// </summary>
public override string Component { get; set; }
/// <summary>
/// 图标
/// </summary>
[Required(ErrorMessage = "Icon不能为空")]
public override string Icon { get; set; }
/// <summary>
/// 父ID
/// </summary>
[Required(ErrorMessage = "ParentId不能为空")]
public override long ParentId { get; set; }
/// <summary>
/// 菜单类型
/// </summary>
public override TargetTypeEnum TargetType { get; set; } = TargetTypeEnum.SELF;
/// <summary>
/// 标题
/// </summary>
[Required(ErrorMessage = "Title不能为空")]
public override string Title { get; set; }
/// <summary>
/// 特殊验证
/// </summary>
/// <param name="validationContext"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//如果菜单类型是菜单
if (TargetType == TargetTypeEnum.SELF)
{
if (string.IsNullOrEmpty(Component))
yield return new ValidationResult("路径不能为空", new[] { nameof(Component) });
}
//设置分类为菜单
Category = ResourceCategoryEnum.MENU;
}
}
/// <summary>
/// 编辑菜单输入参数
/// </summary>
public class MenuEditInput : MenuAddInput
{
/// <summary>
/// ID
/// </summary>
[MinValue(1, ErrorMessage = "Id不能为空")]
public override long Id { get; set; }
}
/// <summary>
/// 菜单树查询参数
/// </summary>
public class MenuPageInput : BasePageInput
{
/// <summary>
/// 父ID
/// </summary>
[Required(ErrorMessage = "ParentId不能为空")]
public long ParentId { get; set; }
}

View File

@@ -0,0 +1,58 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 菜单服务
/// </summary>
public interface IMenuService : ITransient
{
/// <summary>
/// 添加菜单
/// </summary>
/// <param name="input">添加参数</param>
/// <returns></returns>
Task AddAsync(MenuAddInput input);
/// <summary>
/// 删除菜单
/// </summary>
/// <param name="input">删除菜单参数</param>
/// <returns></returns>
Task DeleteAsync(params long[] input);
/// <summary>
/// 详情
/// </summary>
/// <param name="input">id</param>
/// <returns>详细信息</returns>
Task<SysResource> DetailAsync(BaseIdInput input);
/// <summary>
/// 编辑菜单
/// </summary>
/// <param name="input">菜单编辑参数</param>
/// <returns></returns>
Task EditAsync(MenuEditInput input);
/// <summary>
/// 根据模块获取菜单树
/// </summary>
/// <param name="input">菜单树查询参数</param>
/// <returns>菜单树列表</returns>
Task<List<SysResource>> TreeAsync(MenuPageInput input);
}

View File

@@ -0,0 +1,168 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Furion.FriendlyException;
using Mapster;
using SqlSugar;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// <inheritdoc cref="IMenuService"/>
/// </summary>
[Injection(Proxy = typeof(OperDispatchProxy))]
public class MenuService : DbRepository<SysResource>, IMenuService
{
private readonly IRelationService _relationService;
private readonly IResourceService _resourceService;
private readonly IRoleService _roleService;
/// <inheritdoc cref="IMenuService"/>
public MenuService(IResourceService resourceService, IRelationService relationService, IRoleService roleService)
{
_roleService = roleService;
_resourceService = resourceService;
_relationService = relationService;
}
/// <inheritdoc />
[OperDesc("添加菜单")]
public async Task AddAsync(MenuAddInput input)
{
await CheckInputAsync(input);//检查参数
var sysResource = input.Adapt<SysResource>();//实体转换
if (await InsertAsync(sysResource))//插入数据
_resourceService.RefreshCache(ResourceCategoryEnum.MENU);//刷新菜单缓存
}
/// <inheritdoc />
[OperDesc("删除菜单")]
public async Task DeleteAsync(params long[] input)
{
//获取所有ID
var ids = input.ToList();
if (ids.Count > 0)
{
//获取所有菜单和按钮
var resourceList = await _resourceService.GetListByCategorysAsync(new List<ResourceCategoryEnum> { ResourceCategoryEnum.MENU, ResourceCategoryEnum.BUTTON });
//找到要删除的菜单
var sysResources = resourceList.Where(it => ids.Contains(it.Id)).ToList();
//查找内置菜单
var system = sysResources.Where(it => it.Code == ResourceConst.System).FirstOrDefault();
if (system != null)
throw Oops.Bah($"不可删除系统菜单:{system.Title}");
//需要删除的资源ID列表
var resourceIds = new List<long>();
//遍历菜单列表
sysResources.ForEach(it =>
{
//获取菜单所有子节点
var child = _resourceService.GetChildListById(resourceList, it.Id, false);
//将子节点ID添加到删除资源ID列表
resourceIds.AddRange(child.Select(it => it.Id).ToList());
resourceIds.Add(it.Id);//添加到删除资源ID列表
});
ids.AddRange(resourceIds);//添加到删除ID列表
//事务
var result = await itenant.UseTranAsync(async () =>
{
await DeleteByIdsAsync(ids.Cast<object>().ToArray());//删除菜单和按钮
await Context.Deleteable<SysRelation>()//关系表删除对应SYS_ROLE_HAS_RESOURCE
.Where(it => it.Category == CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE && resourceIds.Contains(SqlFunc.ToInt64(it.TargetId))).ExecuteCommandAsync();
});
if (result.IsSuccess)//如果成功了
{
_resourceService.RefreshCache(ResourceCategoryEnum.MENU);//资源表菜单刷新缓存
_resourceService.RefreshCache(ResourceCategoryEnum.BUTTON);//资源表按钮刷新缓存
_relationService.RefreshCache(CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE);//关系表刷新缓存
}
else
{
//写日志
throw Oops.Oh(result.ErrorMessage);
}
}
}
/// <inheritdoc />
public async Task<SysResource> DetailAsync(BaseIdInput input)
{
var sysResources = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.MENU);
var resource = sysResources.Where(it => it.Id == input.Id).FirstOrDefault();
return resource;
}
/// <inheritdoc />
[OperDesc("编辑菜单")]
public async Task EditAsync(MenuEditInput input)
{
await CheckInputAsync(input);//检查参数
var sysResource = input.Adapt<SysResource>();//实体转换
if (await UpdateAsync(sysResource))//更新数据
{
_resourceService.RefreshCache(ResourceCategoryEnum.MENU);//刷新菜单缓存
//需要更新资源权限,因为地址可能改变,页面权限需要更改
await _roleService.RefreshResourceAsync(input.Id);
}
}
/// <inheritdoc />
public async Task<List<SysResource>> TreeAsync(MenuPageInput input)
{
//获取所有菜单
var sysResources = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.MENU);
sysResources = sysResources
.Where(it => it.ParentId == input.ParentId)
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Title == input.SearchKey)//根据关键字查找
.ToList();
//构建菜单树
var tree = _resourceService.ResourceListToTree(sysResources, input.ParentId);
return tree;
}
#region
/// <summary>
/// 检查输入参数
/// </summary>
/// <param name="sysResource"></param>
private async Task CheckInputAsync(SysResource sysResource)
{
//获取所有菜单列表
var menList = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.MENU);
//判断是否有同级且同名的菜单
if (menList.Any(it => it.ParentId == sysResource.ParentId && it.Title == sysResource.Title && it.Id != sysResource.Id))
throw Oops.Bah($"存在重复的菜单名称:{sysResource.Title}");
if (sysResource.ParentId != 0)
{
//获取父级,判断父级ID正不正确
var parent = menList.Where(it => it.Id == sysResource.ParentId).FirstOrDefault();
if (parent != null)
{
if (parent.Id == sysResource.Id)
throw Oops.Bah($"上级菜单不能选择自己");
}
else
{
throw Oops.Bah($"上级菜单不存在:{sysResource.ParentId}");
}
}
}
#endregion
}

View File

@@ -0,0 +1,32 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 通知服务
/// </summary>
public interface INoticeService : ITransient
{
/// <summary>
/// 通知用户下线
/// </summary>
/// <param name="userId">用户ID</param>
/// <param name="verificatInfos">验证列表</param>
/// <param name="message">消息内容</param>
/// <returns></returns>
Task LogoutAsync(long userId, List<VerificatInfo> verificatInfos, string message);
}

View File

@@ -0,0 +1,41 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Microsoft.AspNetCore.SignalR;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// <inheritdoc cref="INoticeService"/>
/// </summary>
public class NoticeService : INoticeService
{
/// <inheritdoc/>
public async Task LogoutAsync(long userId, List<VerificatInfo> verificatInfos, string message)
{
//客户端ID列表
var clientIds = new List<string>();
//遍历token列表获取客户端ID列表
verificatInfos.ForEach(it =>
{
clientIds.AddRange(it.ClientIds);
});
//获取signalr实例
var signalr = App.GetService<IHubContext<SysHub, ISysHub>>();
//发送其他客户端登录消息
await signalr.Clients.Users(clientIds).Logout(message);
}
}

View File

@@ -0,0 +1,26 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 操作日志分页输入
/// </summary>
public class OperateLogPageInput : VisitLogPageInput
{
}
/// <summary>
/// 操作日志分页输入
/// </summary>
public class OperateLogInput : VisitLogInput
{
}

View File

@@ -0,0 +1,49 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 操作日志服务
/// </summary>
public interface IOperateLogService : ITransient
{
/// <summary>
/// 根据分类删除操作日志
/// </summary>
/// <param name="category">分类名称</param>
/// <returns></returns>
Task DeleteAsync(params string[] category);
/// <summary>
/// 导出后台日志
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<MemoryStream> ExportFileAsync(List<SysOperateLog> input = null);
/// <summary>
/// 导出后台日志
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<MemoryStream> ExportFileAsync(OperateLogInput input);
/// <summary>
/// 操作日志分页查询
/// </summary>
/// <param name="input">查询参数</param>
/// <returns>分页列表</returns>
Task<SqlSugarPagedList<SysOperateLog>> PageAsync(OperateLogPageInput input);
}

View File

@@ -0,0 +1,104 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Mapster;
using MiniExcelLibs;
using SqlSugar;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// <inheritdoc cref="IOperateLogService"/>
/// </summary>
[Injection(Proxy = typeof(OperDispatchProxy))]
public class OperateLogService : DbRepository<SysOperateLog>, IOperateLogService
{
/// <inheritdoc />
[OperDesc("删除操作日志")]
public async Task DeleteAsync(params string[] category)
{
await AsDeleteable().Where(it => category.Contains(it.Category)).ExecuteCommandAsync();
}
/// <inheritdoc/>
[OperDesc("导出操作日志", IsRecordPar = false)]
public async Task<MemoryStream> ExportFileAsync(List<SysOperateLog> input = null)
{
input ??= await GetListAsync();
//总数据
Dictionary<string, object> sheets = new();
List<Dictionary<string, object>> devExports = new();
foreach (var devData in input)
{
#region sheet
//变量页
var data = devData.GetType().GetPropertiesWithCache();
Dictionary<string, object> devExport = new();
foreach (var item in data)
{
//描述
var desc = TypeExtensions.FindDisplayAttribute(item);
//数据源增加
devExport.Add(desc ?? item.Name, item.GetValue(devData)?.ToString());
}
devExports.Add(devExport);
#endregion
}
sheets.Add("操作日志", devExports);
var memoryStream = new MemoryStream();
await memoryStream.SaveAsAsync(sheets);
return memoryStream;
}
/// <inheritdoc/>
[OperDesc("导出操作日志", IsRecordPar = false)]
public async Task<MemoryStream> ExportFileAsync(OperateLogInput input)
{
var query = GetPage(input.Adapt<OperateLogPageInput>());
var data = await query.ToListAsync();
return await ExportFileAsync(data);
}
/// <inheritdoc />
public async Task<SqlSugarPagedList<SysOperateLog>> PageAsync(OperateLogPageInput input)
{
var query = GetPage(input);
var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页
return pageInfo;
}
private ISugarQueryable<SysOperateLog> GetPage(OperateLogPageInput input)
{
var query = Context.Queryable<SysOperateLog>()
.WhereIF(!string.IsNullOrEmpty(input.Account), it => it.OpAccount == input.Account)//根据账号查询
.WhereIF(!string.IsNullOrEmpty(input.Category), it => it.Category == input.Category)//根据分类查询
.WhereIF(!string.IsNullOrEmpty(input.ExeStatus), it => it.ExeStatus == input.ExeStatus)//根据结果查询
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Name.Contains(input.SearchKey) || it.OpIp.Contains(input.SearchKey));//根据关键字查询
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
query = query.OrderBy(it => it.Id, OrderByType.Desc);//排序
return query;
}
}

View File

@@ -0,0 +1,99 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 关系服务
/// </summary>
public interface IRelationService : ITransient
{
/// <summary>
/// 获取关系表用户工作台
/// </summary>
/// <param name="userId">用户ID</param>
/// <returns>关系表数据</returns>
Task<SysRelation> GetWorkbenchAsync(long userId);
/// <summary>
/// 根据分类获取关系表信息
/// </summary>
/// <param name="category">分类名称</param>
/// <returns>关系表</returns>
Task<List<SysRelation>> GetRelationByCategoryAsync(string category);
/// <summary>
/// 通过对象ID和分类获取关系列表
/// </summary>
/// <param name="objectId">对象ID</param>
/// <param name="category">分类</param>
/// <returns></returns>
Task<List<SysRelation>> GetRelationListByObjectIdAndCategoryAsync(long objectId, string category);
/// <summary>
/// 通过对象ID列表和分类获取关系列表
/// </summary>
/// <param name="objectIds">对象ID</param>
/// <param name="category">分类</param>
/// <returns></returns>
Task<List<SysRelation>> GetRelationListByObjectIdListAndCategoryAsync(List<long> objectIds, string category);
/// <summary>
/// 通过目标ID和分类获取关系列表
/// </summary>
/// <param name="targetId">目标ID</param>
/// <param name="category">分类</param>
/// <returns></returns>
Task<List<SysRelation>> GetRelationListByTargetIdAndCategoryAsync(string targetId, string category);
/// <summary>
/// 通过目标ID列表和分类获取关系列表
/// </summary>
/// <param name="targetIds"></param>
/// <param name="category"></param>
/// <returns></returns>
Task<List<SysRelation>> GetRelationListByTargetIdListAndCategoryAsync(List<string> targetIds, string category);
/// <summary>
/// 更新缓存
/// </summary>
/// <param name="category">分类</param>
/// <returns></returns>
void RefreshCache(string category);
/// <summary>
/// 保存关系
/// </summary>
/// <param name="category">分类</param>
/// <param name="objectId">对象ID</param>
/// <param name="targetId">目标ID</param>
/// <param name="extJson">拓展信息</param>
/// <param name="clear">是否清除老的数据</param>
/// <param name="refreshCache">是否刷新缓存</param>
/// <returns></returns>
Task SaveRelationAsync(string category, long objectId, string targetId, string extJson, bool clear, bool refreshCache = true);
/// <summary>
/// 批量保存关系
/// </summary>
/// <param name="category">分类</param>
/// <param name="objectId">对象ID</param>
/// <param name="targetIds">目标ID列表</param>
/// <param name="extJsons">拓展信息列表</param>
/// <param name="clear">是否清除老的数据</param>
/// <returns></returns>
Task SaveRelationBatchAsync(string category, long objectId, List<string> targetIds, List<string> extJsons, bool clear);
}

View File

@@ -0,0 +1,145 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.FriendlyException;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <inheritdoc cref="IRelationService"/>
public class RelationService : DbRepository<SysRelation>, IRelationService
{
/// <inheritdoc/>
public async Task<List<SysRelation>> GetRelationByCategoryAsync(string category)
{
//先从Cache拿需要获取新的对象避免操作导致缓存中对象改变
var sysRelations = CacheStatic.Cache.Get<List<SysRelation>>(CacheConst.CACHE_SYSRELATION + category, true);
if (sysRelations == null)
{
//cache没有就去数据库拿
sysRelations = await GetListAsync(it => it.Category == category);
if (sysRelations.Count > 0)
{
//插入Cache
CacheStatic.Cache.Set(CacheConst.CACHE_SYSRELATION + category, sysRelations, true);
}
}
return sysRelations;
}
/// <inheritdoc/>
public async Task<List<SysRelation>> GetRelationListByObjectIdAndCategoryAsync(long objectId, string category)
{
var sysRelations = await GetRelationByCategoryAsync(category);
var result = sysRelations.Where(it => it.ObjectId == objectId).ToList();//获取关系集合
return result;
}
/// <inheritdoc/>
public async Task<List<SysRelation>> GetRelationListByObjectIdListAndCategoryAsync(List<long> objectIds, string category)
{
var sysRelations = await GetRelationByCategoryAsync(category);
var result = sysRelations.Where(it => objectIds.Contains(it.ObjectId)).ToList();//获取关系集合
return result;
}
/// <inheritdoc/>
public async Task<List<SysRelation>> GetRelationListByTargetIdAndCategoryAsync(string targetId, string category)
{
var sysRelations = await GetRelationByCategoryAsync(category);
var result = sysRelations.Where(it => it.TargetId == targetId).ToList();//获取关系集合
return result;
}
/// <inheritdoc/>
public async Task<List<SysRelation>> GetRelationListByTargetIdListAndCategoryAsync(List<string> targetIds, string category)
{
var sysRelations = await GetRelationByCategoryAsync(category);
var result = sysRelations.Where(it => targetIds.Contains(it.TargetId)).ToList();//获取关系集合
return result;
}
/// <inheritdoc/>
public async Task<SysRelation> GetWorkbenchAsync(long userId)
{
var sysRelations = await GetRelationByCategoryAsync(CateGoryConst.Relation_SYS_USER_WORKBENCH_DATA);
var result = sysRelations.FirstOrDefault(it => it.ObjectId == userId);//获取个人工作台
return result;
}
/// <inheritdoc/>
public void RefreshCache(string category)
{
CacheStatic.Cache.Remove(CacheConst.CACHE_SYSRELATION + category);//删除cache
}
/// <inheritdoc/>
public async Task SaveRelationAsync(string category, long objectId, string targetId, string extJson, bool clear, bool refreshCache = true)
{
var sysRelation = new SysRelation
{
ObjectId = objectId,
TargetId = targetId,
Category = category,
ExtJson = extJson
};
//事务
var result = await itenant.UseTranAsync(async () =>
{
if (clear)
await DeleteAsync(it => it.ObjectId == objectId && it.Category == category);//删除老的
await InsertAsync(sysRelation);//添加新的
});
if (result.IsSuccess)//如果成功了
{
if (refreshCache)
RefreshCache(category);
}
else
{
//写日志
throw Oops.Oh(result.ErrorMessage);
}
}
/// <inheritdoc/>
public async Task SaveRelationBatchAsync(string category, long objectId, List<string> targetIds, List<string> extJsons, bool clear)
{
var sysRelations = new List<SysRelation>();//要添加的列表
for (int i = 0; i < targetIds.Count; i++)
{
sysRelations.Add(new SysRelation
{
ObjectId = objectId,
TargetId = targetIds[i],
Category = category,
ExtJson = extJsons?[i]
});
}
var result = await itenant.UseTranAsync(async () =>
{
if (clear)
await DeleteAsync(it => it.ObjectId == objectId && it.Category == category);//删除老的
await InsertRangeAsync(sysRelations);//添加新的
});
if (result.IsSuccess)//如果成功了
{
RefreshCache(category);
}
else
{
throw Oops.Oh(result.ErrorMessage);
}
}
}

View File

@@ -0,0 +1,72 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 角色按钮资源
/// </summary>
public class RoleGrantResourceButton
{
/// <summary>
/// 按钮id
/// </summary>
public long Id { get; set; }
/// <summary>
/// 标题
/// </summary>
public string Title { get; set; }
}
/// <summary>
/// 授权菜单类
/// </summary>
public class RoleGrantResourceMenu
{
/// <summary>
/// 菜单下按钮集合
/// </summary>
public List<RoleGrantResourceButton> Button { get; set; } = new List<RoleGrantResourceButton>();
/// <summary>
/// 菜单id
/// </summary>
public long Id { get; set; }
/// <summary>
/// 父id
/// </summary>
public long ParentId { get; set; }
/// <summary>
/// 父名称
/// </summary>
public string ParentName { get; set; }
/// <summary>
/// 菜单名称
/// </summary>
public string Title { get; set; }
}
/// <summary>
/// Blazor Server的组件路由内容
/// </summary>
public class PermissionTreeSelector
{
/// <summary>
/// 路由名称
/// </summary>
public string ApiRoute { get; set; }
}

View File

@@ -0,0 +1,105 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 资源服务
/// </summary>
public interface IResourceService : ITransient
{
/// <summary>
/// 获取所有的菜单和模块以及单页面列表,并按分类和排序码排序,不会形成树列表
/// </summary>
/// <returns>所有的菜单和模块以及单页面列表</returns>
Task<List<SysResource>> GetaMenuAndSpaListAsync();
/// <summary>
/// 获取子资源
/// </summary>
/// <param name="sysResources"></param>
/// <param name="resId"></param>
/// <param name="isContainOneself"></param>
/// <returns></returns>
List<SysResource> GetChildListById(List<SysResource> sysResources, long resId, bool isContainOneself = true);
/// <summary>
/// 获取ID获取Code列表
/// </summary>
/// <param name="ids">id列表</param>
/// <param name="category">分类</param>
/// <returns>Code列表</returns>
Task<List<string>> GetCodeByIdsAsync(List<long> ids, ResourceCategoryEnum category);
/// <summary>
/// 根据分类获取资源列表
/// </summary>
/// <param name="category">分类名称</param>
/// <returns>资源列表</returns>
Task<List<SysResource>> GetListByCategoryAsync(ResourceCategoryEnum category);
/// <summary>
/// 资源分类列表,如果是空的则获取全部资源
/// </summary>
/// <param name="categorys">资源分类列表</param>
/// <returns></returns>
Task<List<SysResource>> GetListByCategorysAsync(List<ResourceCategoryEnum> categorys = null);
/// <summary>
/// 获取资源所有下级
/// </summary>
/// <param name="resourceList">资源列表</param>
/// <param name="parentId">父ID</param>
/// <returns></returns>
List<SysResource> GetResourceChilden(List<SysResource> resourceList, long parentId);
/// <summary>
/// 获取上级
/// </summary>
/// <returns></returns>
List<SysResource> GetResourceParent(List<SysResource> resourceList, long parentId);
/// <summary>
/// 获取授权菜单
/// </summary>
/// <returns></returns>
Task<List<RoleGrantResourceMenu>> GetRoleGrantResourceMenusAsync();
/// <summary>
/// 刷新缓存
/// </summary>
/// <param name="category">分类名称</param>
/// <returns></returns>
void RefreshCache(ResourceCategoryEnum category);
/// <summary>
/// 构建菜单树形结构
/// </summary>
/// <param name="resourceList">菜单列表</param>
/// <param name="parentId">父ID</param>
/// <returns>菜单形结构</returns>
/// <inheritdoc/>
List<SysResource> ResourceListToTree(List<SysResource> resourceList, long parentId = 0);
/// <summary>
/// 多个树转列表
/// </summary>
/// <param name="data"></param>
List<SysResource> ResourceTreeToList(List<SysResource> data);
/// <summary>
/// 获取PageTabItems
/// </summary>
/// <param name="nav"></param>
/// <returns></returns>
List<PageTabItem> SameLevelMenuPasePageTab(List<SysResource> nav);
}

View File

@@ -0,0 +1,288 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Mapster;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// Tab表示类
/// </summary>
/// <param name="Title">标题</param>
/// <param name="Href">跳转类型</param>
/// <param name="Icon">图标</param>
public record PageTabItem(string Title, string Href, string Icon);
/// <inheritdoc cref="IResourceService"/>
public class ResourceService : DbRepository<SysResource>, IResourceService
{
/// <inheritdoc/>
public async Task<List<SysResource>> GetaMenuAndSpaListAsync()
{
//获取所有的菜单以及单页面
var sysResources = await GetListByCategorysAsync((List<ResourceCategoryEnum>)new() { ResourceCategoryEnum.MENU, ResourceCategoryEnum.SPA });
return sysResources?.OrderBy(it => it.Category).ThenBy(it => it.SortCode).ToList();
}
/// <inheritdoc />
public List<SysResource> GetChildListById(List<SysResource> sysResources, long resId, bool isContainOneself = true)
{
//查找下级
var childLsit = GetResourceChilden(sysResources, resId);
if (isContainOneself)//如果包含自己
{
//获取自己
var self = sysResources.Where(it => it.Id == resId).FirstOrDefault();
if (self != null) childLsit.Insert(0, self);//如果不为空就插到第一个
}
return childLsit;
}
/// <inheritdoc />
public async Task<List<string>> GetCodeByIdsAsync(List<long> ids, ResourceCategoryEnum category)
{
//根据分类获取所有
var sysResources = await GetListByCategoryAsync(category);
return sysResources.Where(it => ids.Contains(it.Id)).Select(it => it.Code).ToList();
}
/// <inheritdoc />
public async Task<List<SysResource>> GetListByCategoryAsync(ResourceCategoryEnum category)
{
//先从Cache拿需要获取新的对象避免操作导致缓存中对象改变
var sysResources = CacheStatic.Cache.Get<List<SysResource>>(CacheConst.CACHE_SYSRESOURCE + category.ToString(), true);
if (sysResources == null)
{
//cache没有就去数据库拿
sysResources = await GetListAsync(it => it.Category == category);
if (sysResources.Count > 0)
{
//插入Cache
CacheStatic.Cache.Set(CacheConst.CACHE_SYSRESOURCE + category.ToString(), sysResources, true);
}
}
return sysResources;
}
/// <inheritdoc/>
public async Task<List<SysResource>> GetListByCategorysAsync(List<ResourceCategoryEnum> categoryList = null)
{
//定义结果
var sysResources = new List<SysResource>();
//定义资源分类列表,如果是空的则获取全部资源
categoryList = categoryList != null ? categoryList
: new List<ResourceCategoryEnum> { ResourceCategoryEnum.MENU, ResourceCategoryEnum.BUTTON, ResourceCategoryEnum.SPA };
//遍历列表
foreach (var category in categoryList)
{
//根据分类获取到资源列表
var data = await GetListByCategoryAsync(category);
//添加到结果集
sysResources.AddRange(data);
}
return sysResources;
}
/// <inheritdoc/>
public List<SysResource> GetResourceChilden(List<SysResource> resourceList, long parentId)
{
//找下级资源ID列表
var resources = resourceList.Where(it => it.ParentId == parentId).ToList();
if (resources.Count > 0)//如果数量大于0
{
var data = new List<SysResource>();
foreach (var item in resources)//遍历资源
{
var res = GetResourceChilden(resourceList, item.Id);
data.AddRange(res);//添加子节点;
data.Add(item);//添加到列表
}
return data;//返回结果
}
return new List<SysResource>();
}
/// <inheritdoc/>
public List<SysResource> GetResourceParent(List<SysResource> resourceList, long parentId)
{
//找上级资源ID列表
var resources = resourceList.Where(it => it.Id == parentId).FirstOrDefault();
if (resources != null)//如果数量大于0
{
var data = new List<SysResource>();
var parents = GetResourceParent(resourceList, resources.ParentId);
data.AddRange(parents);//添加子节点;
data.Add(resources);//添加到列表
return data;//返回结果
}
return new List<SysResource>();
}
/// <inheritdoc/>
public async Task<List<RoleGrantResourceMenu>> GetRoleGrantResourceMenusAsync()
{
var roleGrantResourceMenus = new List<RoleGrantResourceMenu>();//定义结果
List<SysResource> allMenuList = (await GetListByCategoryAsync(ResourceCategoryEnum.MENU));//获取所有菜单列表
List<SysResource> allButtonList = await GetListByCategoryAsync(ResourceCategoryEnum.BUTTON);//获取所有按钮列表
var parentMenuList = allMenuList.Where(it => it.ParentId == 0).ToList();//获取一级目录
//遍历一级目录
foreach (var parent in parentMenuList)
{
//如果是目录则去遍历下级
if (parent.TargetType == TargetTypeEnum.None)
{
//获取所有下级菜单
var menuList = GetChildListById(allMenuList, parent.Id, false);
//遍历下级菜单
foreach (var menu in menuList)
{
//如果菜单类型是菜单
if (menu.TargetType == TargetTypeEnum.SELF)
{
//获取菜单下按钮集合并转换成对应实体
var buttonList = allButtonList.Where(it => it.ParentId == menu.Id).ToList();
var buttons = buttonList.Adapt<List<RoleGrantResourceButton>>();
roleGrantResourceMenus.Add(new()
{
Id = menu.Id,
ParentId = parent.Id,
ParentName = parent.Title,
Title = GetRoleGrantResourceMenuTitle(parentMenuList, menu),//菜单名称需要特殊处理因为有二级菜单
Button = buttons
});
}
else if (menu.TargetType == TargetTypeEnum.BLANK || menu.TargetType == TargetTypeEnum.CALLBACK)//如果是内链或者外链
{
//直接加到资源列表
roleGrantResourceMenus.Add(new()
{
Id = menu.Id,
ParentId = parent.Id,
ParentName = parent.Title,
Title = menu.Title,
});
}
}
}
else
{
//否则就将自己加到一级目录里面
roleGrantResourceMenus.Add(new()
{
Id = parent.Id,
ParentId = parent.Id,
ParentName = parent.Title,
Title = parent.Title,
});
}
}
return roleGrantResourceMenus;
}
/// <inheritdoc/>
public void RefreshCache(ResourceCategoryEnum category)
{
//如果分类是空的
if (category == ResourceCategoryEnum.None)
{
//删除全部key
CacheStatic.Cache.Remove(CacheConst.CACHE_SYSRESOURCE + ResourceCategoryEnum.SPA.ToString());
CacheStatic.Cache.Remove(CacheConst.CACHE_SYSRESOURCE + ResourceCategoryEnum.BUTTON.ToString());
CacheStatic.Cache.Remove(CacheConst.CACHE_SYSRESOURCE + ResourceCategoryEnum.MENU.ToString());
}
else
{
//否则只删除一个Key
CacheStatic.Cache.Remove(CacheConst.CACHE_SYSRESOURCE + category.ToString());
}
}
/// <inheritdoc />
public List<SysResource> ResourceListToTree(List<SysResource> resourceList, long parentId = 0)
{
//找下级资源ID列表
var resources = resourceList
.Where(it => it.ParentId == parentId).OrderBy(it => it.SortCode).ToList();
if (resources.Count > 0)//如果数量大于0
{
var data = new List<SysResource>();
foreach (var item in resources)//遍历资源
{
var children = ResourceListToTree(resourceList, item.Id);//添加子节点
item.Children = children.Count > 0 ? children : null;
data.Add(item);//添加到列表
}
return data;//返回结果
}
return new List<SysResource>();
}
/// <inheritdoc />
public List<SysResource> ResourceTreeToList(List<SysResource> data)
{
List<SysResource> list = new();
foreach (var item in data)
{
list.Add(item);
if (item.Children != null && item.Children.Count > 0)
{
list.AddRange(ResourceTreeToList(item.Children));
}
}
return list;
}
/// <inheritdoc />
public List<PageTabItem> SameLevelMenuPasePageTab(List<SysResource> nav)
{
List<PageTabItem> pageTabItems = new();
if (nav == null) return pageTabItems;
foreach (var item in nav)
{
if ((item.Category == ResourceCategoryEnum.MENU || item.Category == ResourceCategoryEnum.SPA) && item.TargetType == TargetTypeEnum.SELF)
{
if (item.Icon == null)
pageTabItems.Add(new PageTabItem(item.Title, item.Component, ""));
else
pageTabItems.Add(new PageTabItem(item.Title, item.Component, item.Icon));
}
}
return pageTabItems;
}
/// <summary>
/// 获取授权菜单类菜单名称
/// </summary>
/// <param name="menuList">菜单列表</param>
/// <param name="menu">当前菜单</param>
/// <returns></returns>
private string GetRoleGrantResourceMenuTitle(List<SysResource> menuList, SysResource menu)
{
//查找菜单上级
var parentList = GetResourceParent(menuList, menu.ParentId);
//如果有父级菜单
if (parentList.Count > 0)
{
var titles = parentList.Select(it => it.Title).ToList();//提取出父级的name
var title = string.Join("- ", titles) + $"-{menu.Title}";//根据-分割,转换成字符串并在最后加上菜单的title
return title;
}
else
{
return menu.Title;//原路返回
}
}
}

View File

@@ -0,0 +1,84 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel.DataAnnotations;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 角色授权资源参数
/// </summary>
public class GrantResourceInput : RoleOwnResourceOutput
{
/// <summary>
/// 授权资源信息
/// </summary>
[Required(ErrorMessage = "授权资源信息表不能为空")]
public override List<RelationRoleResuorce> GrantInfoList { get; set; }
/// <summary>
/// 角色Id
/// </summary>
[MinValue(1, ErrorMessage = "Id不能为空")]
public override long Id { get; set; }
}
/// <summary>
/// 角色授权用户参数
/// </summary>
public class GrantUserInput
{
/// <summary>
/// 授权权限信息
/// </summary>
[Required(ErrorMessage = "GrantInfoList不能为空")]
public List<long> GrantInfoList { get; set; }
/// <summary>
/// Id
/// </summary>
[Required(ErrorMessage = "Id不能为空")]
public long? Id { get; set; }
}
/// <summary>
/// 角色添加参数
/// </summary>
public class RoleAddInput : SysRole
{
/// <summary>
/// 名称
/// </summary>
[Required(ErrorMessage = "Name不能为空")]
public override string Name { get; set; }
}
/// <summary>
/// 角色编辑参数
/// </summary>
public class RoleEditInput : RoleAddInput
{
/// <summary>
/// Id
/// </summary>
[MinValue(1, ErrorMessage = "Id不能为空")]
public override long Id { get; set; }
}
/// <summary>
/// 角色查询参数
/// </summary>
public class RolePageInput : BasePageInput
{
}

View File

@@ -0,0 +1,107 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 角色服务
/// </summary>
public interface IRoleService : ITransient
{
/// <summary>
/// 添加角色
/// </summary>
/// <param name="input">添加参数</param>
/// <returns></returns>
Task AddAsync(RoleAddInput input);
/// <summary>
/// 删除角色
/// </summary>
/// <param name="input">删除参数</param>
/// <returns></returns>
Task DeleteAsync(params long[] input);
/// <summary>
/// 编辑角色
/// </summary>
/// <param name="input">编辑角色</param>
/// <returns></returns>
Task EditAsync(RoleEditInput input);
/// <summary>
/// 根据用户ID获取用户角色Id集合
/// </summary>
/// <param name="userId">用户ID</param>
/// <returns></returns>
Task<List<long>> GetRoleIdListByUserIdAsync(long userId);
/// <summary>
/// 根据用户ID获取用户角色集合
/// </summary>
/// <param name="userId">用户ID</param>
/// <returns></returns>
Task<List<SysRole>> GetRoleListByUserIdAsync(long userId);
/// <summary>
/// 给角色授权资源
/// </summary>
/// <param name="input">授权参数</param>
/// <returns></returns>
Task GrantResourceAsync(GrantResourceInput input);
/// <summary>
/// 给角色授权用户
/// </summary>
/// <param name="input">授权信息</param>
/// <returns></returns>
Task GrantUserAsync(GrantUserInput input);
/// <summary>
/// 角色拥有资源
/// </summary>
/// <param name="input">角色id</param>
/// <returns>角色拥有资源信息</returns>
Task<RoleOwnResourceOutput> OwnResourceAsync(long input);
/// <summary>
/// 获取角色下的用户
/// </summary>
/// <param name="input">角色ID</param>
/// <returns></returns>
Task<List<long>> OwnUserAsync(long input);
/// <summary>
/// 分页查询角色
/// </summary>
/// <param name="input">查询参数</param>
/// <returns></returns>
Task<SqlSugarPagedList<SysRole>> PageAsync(RolePageInput input);
/// <summary>
/// 刷新缓存
/// </summary>
/// <returns></returns>
void RefreshCache();
/// <summary>
/// 角色刷新资源
/// </summary>
Task RefreshResourceAsync(long? menuId = null);
/// <summary>
/// 角色选择器
/// </summary>
Task<List<SysRole>> RoleSelectorAsync(string searchKey = null);
}

View File

@@ -0,0 +1,406 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Furion.EventBus;
using Furion.FriendlyException;
using Mapster;
using ThingsGateway.Admin.Core;
using ThingsGateway.Admin.Core.JsonExtensions;
using Yitter.IdGenerator;
namespace ThingsGateway.Admin.Application
{
/// <inheritdoc cref="IRoleService"/>
[Injection(Proxy = typeof(OperDispatchProxy))]
public class RoleService : DbRepository<SysRole>, IRoleService
{
private readonly IEventPublisher _eventPublisher;
private readonly IRelationService _relationService;
private readonly IResourceService _resourceService;
/// <inheritdoc cref="IRoleService"/>
public RoleService(
IRelationService relationService,
IResourceService resourceService,
IEventPublisher eventPublisher)
{
_relationService = relationService;
_resourceService = resourceService;
_eventPublisher = eventPublisher;
}
/// <inheritdoc />
[OperDesc("添加角色")]
public async Task AddAsync(RoleAddInput input)
{
await CheckInput(input);//检查参数
var sysRole = input.Adapt<SysRole>();//实体转换
sysRole.Code = YitIdHelper.NextId().ToString();//赋值Code
if (await InsertAsync(sysRole))//插入数据
RefreshCache();//刷新缓存
}
/// <inheritdoc />
[OperDesc("删除角色")]
public async Task DeleteAsync(params long[] input)
{
//获取所有ID
var ids = input.ToList();
if (ids.Count > 0)
{
var sysRoles = await GetListAsync();//获取所有角色
var hasSuperAdmin = sysRoles.Any(it => it.Code == RoleConst.SuperAdmin && ids.Contains(it.Id));//判断是否有超级管理员
if (hasSuperAdmin) throw Oops.Bah($"不可删除系统内置超管角色");
//数据库是string所以这里转下
var targetIds = ids.Select(it => it.ToString()).ToList();
//定义删除的关系
var delRelations = new List<string> { CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE, CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION };
//事务
var result = await itenant.UseTranAsync(async () =>
{
await DeleteByIdsAsync(ids.Cast<object>().ToArray());//删除按钮
//删除关系表角色与资源关系,角色与权限关系
await Context.Deleteable<SysRelation>().Where(it => ids.Contains(it.ObjectId) && delRelations.Contains(it.Category)).ExecuteCommandAsync();
//删除关系表角色与用户关系
await Context.Deleteable<SysRelation>().Where(it => targetIds.Contains(it.TargetId) && it.Category == CateGoryConst.Relation_SYS_USER_HAS_ROLE).ExecuteCommandAsync();
});
if (result.IsSuccess)//如果成功了
{
RefreshCache();//刷新缓存
_relationService.RefreshCache(CateGoryConst.Relation_SYS_USER_HAS_ROLE);//关系表刷新SYS_USER_HAS_ROLE缓存
_relationService.RefreshCache(CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE);//关系表刷新SYS_ROLE_HAS_RESOURCE缓存
_relationService.RefreshCache(CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION);//关系表刷新SYS_ROLE_HAS_PERMISSION缓存
await _eventPublisher.PublishAsync(EventSubscriberConst.ClearUserCache, ids);//清除角色下用户缓存
}
else
{
//写日志
throw Oops.Oh(result.ErrorMessage);
}
}
}
/// <inheritdoc />
[OperDesc("编辑角色")]
public async Task EditAsync(RoleEditInput input)
{
//判断是否超管
if (input.Code == RoleConst.SuperAdmin)
throw Oops.Bah($"不可编辑超管角色");
await CheckInput(input);//检查参数
var role = await GetFirstAsync(it => it.Id == input.Id);//获取角色
if (role != null)
{
var permissions = new List<SysRelation>();
var sysRole = input.Adapt<SysRole>();//实体转换
//事务
var result = await itenant.UseTranAsync(async () =>
{
await UpdateAsync(sysRole);//更新角色
if (permissions.Any())//如果有授权权限就更新
await Context.Updateable(permissions).ExecuteCommandAsync();
});
if (result.IsSuccess)//如果成功了
{
RefreshCache();//刷新缓存
if (permissions.Any())//如果有授权
_relationService.RefreshCache(CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION);//关系表刷新SYS_ROLE_HAS_PERMISSION缓存
await _eventPublisher.PublishAsync(EventSubscriberConst.ClearUserCache, new List<long> { input.Id });//清除角色下用户缓存
}
else
{
//写日志
throw Oops.Oh(result.ErrorMessage);
}
}
}
/// <summary>
/// 获取所有角色
/// </summary>
/// <returns></returns>
public override async Task<List<SysRole>> GetListAsync()
{
//先从Cache拿需要获取新的对象避免操作导致缓存中对象改变
var sysRoles = CacheStatic.Cache.Get<List<SysRole>>(CacheConst.CACHE_SYSROLE, true);
if (sysRoles == null)
{
//cache没有就去数据库拿
sysRoles = await base.GetListAsync();
if (sysRoles.Count > 0)
{
//插入Cache
CacheStatic.Cache.Set(CacheConst.CACHE_SYSROLE, sysRoles, true);
}
}
return sysRoles;
}
/// <inheritdoc/>
public async Task<List<long>> GetRoleIdListByUserIdAsync(long userId)
{
List<SysRole> cods = new();//角色代码集合
var roleList = await _relationService.GetRelationListByObjectIdAndCategoryAsync(userId, CateGoryConst.Relation_SYS_USER_HAS_ROLE);//根据用户ID获取角色ID
var roleIdList = roleList.Select(x => x.TargetId.ToLong()).ToList();//角色ID列表
return roleIdList;
}
/// <inheritdoc/>
public async Task<List<SysRole>> GetRoleListByUserIdAsync(long userId)
{
List<SysRole> cods = new();//角色代码集合
var roleList = await _relationService.GetRelationListByObjectIdAndCategoryAsync(userId, CateGoryConst.Relation_SYS_USER_HAS_ROLE);//根据用户ID获取角色ID
var roleIdList = roleList.Select(x => x.TargetId.ToLong()).ToList();//角色ID列表
if (roleIdList.Count > 0)
{
cods = await GetListAsync(it => roleIdList.Contains(it.Id));
}
return cods;
}
/// <inheritdoc />
[OperDesc("角色授权")]
public async Task GrantResourceAsync(GrantResourceInput input)
{
var menuIds = input.GrantInfoList.Select(it => it.MenuId).ToList();//菜单ID
var extJsons = input.GrantInfoList.Select(it => it.ToJsonString()).ToList();//拓展信息
var relationRoles = new List<SysRelation>();//要添加的角色资源和授权关系表
var sysRole = (await GetListAsync()).Where(it => it.Id == input.Id).FirstOrDefault();//获取角色
if (sysRole != null)
{
#region
//遍历角色列表
for (int i = 0; i < menuIds.Count; i++)
{
//将角色资源添加到列表
relationRoles.Add(new SysRelation
{
ObjectId = sysRole.Id,
TargetId = menuIds[i].ToString(),
Category = CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE,
ExtJson = extJsons?[i]
});
}
#endregion
#region
var relationRolePer = new List<SysRelation>();//要添加的角色有哪些权限列表
//获取菜单信息
var menus = await GetMenuByMenuIds(menuIds);
if (menus.Count > 0)
{
//获取权限授权树
var permissions = PermissionUtil.PermissionTreeSelector(menus.Select(it => it.Component).ToList());
permissions.ForEach(it =>
{
//新建角色权限关系
relationRolePer.Add(new SysRelation
{
ObjectId = sysRole.Id,
TargetId = it.ApiRoute,
Category = CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION,
ExtJson = new RelationRolePermission
{
ApiUrl = it.ApiRoute,
}.ToJsonString()
});
});
}
relationRoles.AddRange(relationRolePer);//合并列表
#endregion
#region
//事务
var result = await itenant.UseTranAsync(async () =>
{
//删除老的
await Context.Deleteable<SysRelation>().Where(it => it.ObjectId == sysRole.Id
&&
(it.Category == CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION
|| it.Category == CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE)
)
.ExecuteCommandAsync();
await Context.Insertable(relationRoles).ExecuteCommandAsync();
});
if (result.IsSuccess)//如果成功了
{
_relationService.RefreshCache(CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE);//刷新关系缓存
_relationService.RefreshCache(CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION);//刷新关系缓存
await _eventPublisher.PublishAsync(EventSubscriberConst.ClearUserCache, new List<long> { input.Id });//发送事件清除角色下用户缓存
}
else
{
//写日志
throw Oops.Oh(result.ErrorMessage);
}
#endregion
}
}
/// <inheritdoc />
[OperDesc("用户授权")]
public async Task GrantUserAsync(GrantUserInput input)
{
var sysRelations = new List<SysRelation>();//关系列表
//遍历用户ID
input.GrantInfoList.ForEach(it =>
{
sysRelations.Add(new SysRelation
{
ObjectId = it,
TargetId = input.Id.ToString(),
Category = CateGoryConst.Relation_SYS_USER_HAS_ROLE
});
});
//事务
var result = await itenant.UseTranAsync(async () =>
{
//删除老的
await Context.Deleteable<SysRelation>().Where(it => it.TargetId == input.Id.ToString() && it.Category == CateGoryConst.Relation_SYS_USER_HAS_ROLE).ExecuteCommandAsync();
await Context.Insertable(sysRelations).ExecuteCommandAsync();//添加新的
});
if (result.IsSuccess)//如果成功了
{
_relationService.RefreshCache(CateGoryConst.Relation_SYS_USER_HAS_ROLE);//刷新关系表SYS_USER_HAS_ROLE缓存
await _eventPublisher.PublishAsync(EventSubscriberConst.ClearUserCache, new List<long> { input.Id.Value });//清除角色下用户缓存
}
else
{
//写日志
throw Oops.Oh(result.ErrorMessage);
}
}
/// <inheritdoc />
public async Task<RoleOwnResourceOutput> OwnResourceAsync(long input)
{
RoleOwnResourceOutput roleOwnResource = new() { Id = input };//定义结果集
List<RelationRoleResuorce> GrantInfoList = new();//已授权信息集合
//获取关系列表
var relations = await _relationService.GetRelationListByObjectIdAndCategoryAsync(input, CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE);
//遍历关系表
relations.ForEach(it =>
{
//将扩展信息转为实体
var relationRole = it.ExtJson.ToJsonWithT<RelationRoleResuorce>();
GrantInfoList.Add(relationRole);//添加到已授权信息
});
roleOwnResource.GrantInfoList = GrantInfoList;//赋值已授权信息
return roleOwnResource;
}
/// <inheritdoc />
public async Task<List<long>> OwnUserAsync(long input)
{
//获取关系列表
var relations = await _relationService.GetRelationListByTargetIdAndCategoryAsync(input.ToString(), CateGoryConst.Relation_SYS_USER_HAS_ROLE);
return relations.Select(it => it.ObjectId).ToList();
}
/// <inheritdoc/>
public async Task<SqlSugarPagedList<SysRole>> PageAsync(RolePageInput input)
{
var query = Context.Queryable<SysRole>()
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Name.Contains(input.SearchKey));//根据关键字查询
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
query = query.OrderBy(it => it.SortCode);//排序
var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页
return pageInfo;
}
/// <inheritdoc />
public void RefreshCache()
{
CacheStatic.Cache.Remove(CacheConst.CACHE_SYSROLE);//删除KEY
}
/// <inheritdoc />
public async Task RefreshResourceAsync(long? menuId = null)
{
var data = await GetListAsync();
foreach (var item in data)
{
var r1 = await OwnResourceAsync(item.Id);
if (menuId == null || r1.GrantInfoList.Any(a => a.MenuId == menuId))
{
await GrantResourceAsync(new GrantResourceInput() { Id = item.Id, GrantInfoList = r1.GrantInfoList });
}
}
}
/// <inheritdoc />
public async Task<List<SysRole>> RoleSelectorAsync(string searchKey = null)
{
var result = await Context.Queryable<SysRole>()
.WhereIF(!string.IsNullOrEmpty(searchKey), it => it.Name.Contains(searchKey))//根据关键字查询
.ToListAsync();
return result;
}
#region
/// <summary>
/// 检查输入参数
/// </summary>
/// <param name="sysRole"></param>
private async Task CheckInput(SysRole sysRole)
{
var sysRoles = await GetListAsync();//获取所有
var repeatName = sysRoles.Any(it => it.Name == sysRole.Name && it.Id != sysRole.Id);//是否有重复角色名称
if (repeatName)//如果有
{
throw Oops.Bah($"存在重复的角色:{sysRole.Name}");
}
}
/// <summary>
/// 根据菜单ID获取菜单
/// </summary>
/// <param name="menuIds"></param>
/// <returns></returns>
private async Task<List<SysResource>> GetMenuByMenuIds(List<long> menuIds)
{
//获取所有菜单
var menuList = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.MENU);
//获取菜单信息
var menus = menuList.Where(it => menuIds.Contains(it.Id)).ToList();
return menus;
}
#endregion
}
}

View File

@@ -0,0 +1,58 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 会话分页查询
/// </summary>
public class SessionPageInput : BasePageInput
{
/// <summary>
/// 账号
/// </summary>
[Description("账号")]
public string Account { get; set; }
/// <summary>
/// 最新登录IP
/// </summary>
[Description("最新登录IP")]
public string LatestLoginIp { get; set; }
/// <summary>
/// 姓名
/// </summary>
[Description("姓名")]
public string Name { get; set; }
}
/// <summary>
/// 退出参数
/// </summary>
public class ExitVerificatInput : BaseIdInput
{
/// <summary>
/// 验证ID列表
/// </summary>
[Required(ErrorMessage = "VerificatIds不能为空")]
public List<long> VerificatIds { get; set; }
/// <summary>
/// 用户Id
/// </summary>
[MinValue(1, ErrorMessage = "Id不能为空")]
public override long Id { get; set; }
}

View File

@@ -0,0 +1,64 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 会话输出
/// </summary>
public class SessionOutput : PrimaryKeyEntity
{
/// <summary>
/// 账号
///</summary>
[Description("账号")]
[DataTable(Order = 1, IsShow = true, Sortable = true)]
public virtual string Account { get; set; }
/// <summary>
/// 最新登录ip
///</summary>
[Description("最新登录ip")]
[DataTable(Order = 3, IsShow = true, Sortable = true)]
public string LatestLoginIp { get; set; }
/// <summary>
/// 最新登录时间
///</summary>
[Description("最新登录时间")]
[DataTable(Order = 4, IsShow = true, Sortable = true)]
public DateTime? LatestLoginTime { get; set; }
/// <summary>
/// 在线状态
/// </summary>
[Description("在线状态")]
[DataTable(Order = 2, IsShow = true, Sortable = true)]
public bool OnlineStatus { get; set; }
/// <summary>
/// 令牌数量
/// </summary>
[Description("令牌数量")]
[DataTable(Order = 5, IsShow = true, Sortable = true)]
public int VerificatCount { get; set; }
/// <summary>
/// 令牌信息集合
/// </summary>
[Description("令牌列表")]
public List<VerificatInfo> VerificatSignList { get; set; }
}

View File

@@ -0,0 +1,42 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 会话管理服务
/// </summary>
public interface ISessionService : ITransient
{
/// <summary>
/// 强退会话
/// </summary>
/// <param name="input">用户ID</param>
Task ExitSessionAsync(long input);
/// <summary>
/// 强退verificat
/// </summary>
/// <param name="input">verificat列表</param>
Task ExitVerificatAsync(ExitVerificatInput input);
/// <summary>
/// 会话分页查询
/// </summary>
/// <param name="input">查询参数</param>
/// <returns>会话列表</returns>
Task<SqlSugarPagedList<SessionOutput>> PageAsync(SessionPageInput input);
}

View File

@@ -0,0 +1,118 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using SqlSugar;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// <inheritdoc cref="ISessionService"/>
/// </summary>
[Injection(Proxy = typeof(OperDispatchProxy))]
public class SessionService : DbRepository<SysUser>, ISessionService
{
private readonly INoticeService _noticeService;
private readonly IVerificatService _verificatService;
/// <inheritdoc cref="ISessionService"/>
public SessionService(IVerificatService verificatService, INoticeService noticeService)
{
_verificatService = verificatService;
_noticeService = noticeService;
}
/// <inheritdoc/>
[OperDesc("强退会话")]
public async Task ExitSessionAsync(long input)
{
//verificat列表
List<VerificatInfo> verificatInfos = await _verificatService.GetVerificatIdAsync(input);
//从列表中删除
await _verificatService.SetVerificatIdAsync(input, new());
var message = "您已被强制下线!";
await _noticeService.LogoutAsync(input, verificatInfos, message);//通知下线
}
/// <inheritdoc/>
[OperDesc("强退令牌")]
public async Task ExitVerificatAsync(ExitVerificatInput input)
{
//获取该用户的verificat信息
List<VerificatInfo> verificatInfos = await _verificatService.GetVerificatIdAsync(input.Id);
//踢掉包含verificat列表的verificat信息
var setVerificats = verificatInfos.Where(it => !input.VerificatIds.Contains(it.Id)).ToList();
var deleteVerificats = verificatInfos.Where(it => input.VerificatIds.Contains(it.Id)).ToList();
await _verificatService.SetVerificatIdAsync(input.Id, setVerificats);//如果还有verificat则更新verificat
var message = "您已被强制下线!";
await _noticeService.LogoutAsync(input.Id, deleteVerificats, message);//通知下线
}
/// <inheritdoc/>
public async Task<SqlSugarPagedList<SessionOutput>> PageAsync(SessionPageInput input)
{
var query = Context.Queryable<SysUser>()
.WhereIF(!string.IsNullOrEmpty(input.Account), it => it.Account.Contains(input.Account))//根据账号查询
.WhereIF(!string.IsNullOrEmpty(input.LatestLoginIp), it => it.LatestLoginIp.Contains(input.LatestLoginIp))//根据IP查询
.OrderBy(it => it.LatestLoginTime, OrderByType.Desc)
.Select<SessionOutput>()
.Mapper(async it =>
{
var verificatInfos = await _verificatService.GetVerificatIdAsync(it.Id);
if (verificatInfos != null)
{
GetVerificatInfos(ref verificatInfos);//获取剩余时间
it.VerificatCount = verificatInfos.Count;//令牌数量
it.VerificatSignList = verificatInfos;//令牌列表
//如果有客户端ID就是在线
it.OnlineStatus = verificatInfos.Any(it => it.ClientIds.Count > 0);
}
else
{
it.VerificatSignList = new();
}
});
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页
return pageInfo;
}
#region
/// <summary>
/// 获取verificat剩余时间信息
/// </summary>
private void GetVerificatInfos(ref List<VerificatInfo> verificatInfos)
{
verificatInfos.ForEach(it =>
{
var now = SysDateTimeExtensions.CurrentDateTime;
it.VerificatRemain = now.GetDiffTime(it.VerificatTimeout);//获取时间差
var verificatSecond = it.VerificatTimeout.AddMinutes(-it.Expire).ToLong();//颁发时间转为时间戳
var timeoutSecond = it.VerificatTimeout.ToLong();//过期时间转为时间戳
});
}
#endregion
}

View File

@@ -0,0 +1,69 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel.DataAnnotations;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 单页输入参数
/// </summary>
public class SpaAddInput : SysResource
{
/// <summary>
/// 路径
/// </summary>
[Required(ErrorMessage = "Component不能为空")]
public override string Component { get; set; }
/// <summary>
/// 图标
/// </summary>
[Required(ErrorMessage = "Icon不能为空")]
public override string Icon { get; set; }
/// <summary>
/// 菜单类型
/// </summary>
public override TargetTypeEnum TargetType { get; set; } = TargetTypeEnum.SELF;
/// <summary>
/// 标题
/// </summary>
[Required(ErrorMessage = "Title不能为空")]
public override string Title { get; set; }
}
/// <summary>
/// 单页输入参数
/// </summary>
public class SpaPageInput : BasePageInput
{
/// <summary>
/// 跳转类型
/// </summary>
public TargetTypeEnum TargetType { get; set; }
}
/// <summary>
/// 单页修改参数
/// </summary>
public class SpaEditInput : SpaAddInput
{
/// <summary>
/// ID
/// </summary>
[MinValue(1, ErrorMessage = "Id不能为空")]
public override long Id { get; set; }
}

View File

@@ -0,0 +1,51 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 单页服务
/// </summary>
public interface ISpaService : ITransient
{
/// <summary>
/// 添加单页
/// </summary>
/// <param name="input">添加参数</param>
/// <returns></returns>
Task AddAsync(SpaAddInput input);
/// <summary>
/// 删除单页
/// </summary>
/// <param name="input">删除参数</param>
/// <returns></returns>
Task DeleteAsync(params long[] input);
/// <summary>
/// 编辑单页
/// </summary>
/// <param name="input">编辑参数</param>
/// <returns></returns>
Task EditAsync(SpaEditInput input);
/// <summary>
/// 分页查询
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<SqlSugarPagedList<SysResource>> PageAsync(SpaPageInput input);
}

View File

@@ -0,0 +1,127 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using Furion.FriendlyException;
using Mapster;
using ThingsGateway.Admin.Core;
using Yitter.IdGenerator;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// <inheritdoc cref="ISpaService"/>
/// </summary>
[Injection(Proxy = typeof(OperDispatchProxy))]
public class SpaService : DbRepository<SysResource>, ISpaService
{
private readonly IResourceService _resourceService;
/// <inheritdoc cref="ISpaService"/>
public SpaService(IResourceService resourceService)
{
this._resourceService = resourceService;
}
/// <inheritdoc />
[OperDesc("添加单页")]
public async Task AddAsync(SpaAddInput input)
{
CheckInput(input);//检查参数
input.Code = YitIdHelper.NextId().ToString();//code取随机值
var sysResource = input.Adapt<SysResource>();//实体转换
if (await InsertAsync(sysResource))//插入数据
_resourceService.RefreshCache(ResourceCategoryEnum.SPA);//刷新缓存
}
/// <inheritdoc />
[OperDesc("删除单页")]
public async Task DeleteAsync(params long[] input)
{
//获取所有ID
var ids = input.ToList();
if (ids.Count > 0)
{
//获取所有
var resourceList = await _resourceService.GetListByCategoryAsync(ResourceCategoryEnum.SPA);
//找到要删除的
var sysresources = resourceList.Where(it => ids.Contains(it.Id)).ToList();
//查找内置单页面
var system = sysresources.Where(it => it.Code == ResourceConst.System).FirstOrDefault();
if (system != null)
throw Oops.Bah($"不可删除系统内置单页面:{system.Title}");
//删除菜单
await DeleteAsync(sysresources);
_resourceService.RefreshCache(ResourceCategoryEnum.SPA);//刷新缓存
}
}
/// <inheritdoc />
[OperDesc("编辑单页")]
public async Task EditAsync(SpaEditInput input)
{
CheckInput(input);//检查参数
var sysResource = input.Adapt<SysResource>();//实体转换
if (await UpdateAsync(sysResource))//更新数据
_resourceService.RefreshCache(ResourceCategoryEnum.SPA);//刷新缓存
}
/// <inheritdoc/>
public async Task<SqlSugarPagedList<SysResource>> PageAsync(SpaPageInput input)
{
var query = Context.Queryable<SysResource>()
.Where(it => it.Category == ResourceCategoryEnum.SPA)//单页
.WhereIF(input.TargetType != 0, it => it.TargetType == input.TargetType)//根据菜单类型查询
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Title.Contains(input.SearchKey) || it.Component.Contains(input.SearchKey));//根据关键字查询
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
query = query.OrderBy(it => it.SortCode);//排序
var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页
return pageInfo;
}
#region
/// <summary>
/// 检查输入参数
/// </summary>
/// <param name="sysResource"></param>
private void CheckInput(SysResource sysResource)
{
//判断菜单类型
if (sysResource.TargetType == TargetTypeEnum.SELF)
{
if (string.IsNullOrEmpty(sysResource.Component))
{
throw Oops.Bah($"组件地址不能为空");
}
}
else if (sysResource.TargetType == TargetTypeEnum.BLANK)//如果是内链或者外链
{
sysResource.Component = null;
}
else
{
throw Oops.Bah($"单页类型错误:{sysResource.TargetType}");//都不是
}
//设置为单页
sysResource.Category = ResourceCategoryEnum.SPA;
}
#endregion
}

View File

@@ -0,0 +1,78 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using SqlSugar;
using System.ComponentModel.DataAnnotations;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 添加用户参数
/// </summary>
public class UserAddInput : SysUser
{
/// <summary>
/// 账号
/// </summary>
[Required(ErrorMessage = "账号不能为空"), MinLength(3, ErrorMessage = "账号不能少于4个字符")]
public override string Account { get; set; }
}
/// <summary>
/// 编辑用户参数
/// </summary>
public class UserEditInput : UserAddInput
{
/// <summary>
/// 账号
/// </summary>
[Required(ErrorMessage = "账号不能为空"), MinLength(3, ErrorMessage = "账号不能少于4个字符")]
public override string Account { get; set; }
/// <summary>
/// Id
/// </summary>
[MinValue(1, ErrorMessage = "Id不能为空")]
public override long Id { get; set; }
}
/// <summary>
/// 用户分页查询参数
/// </summary>
public class UserPageInput : BasePageInput
{
/// <summary>
/// 动态查询条件
/// </summary>
public Expressionable<SysUser> Expression { get; set; }
}
/// <summary>
/// 用户授权角色参数
/// </summary>
public class UserGrantRoleInput
{
/// <summary>
/// Id
/// </summary>
[Required(ErrorMessage = "Id不能为空")]
public long Id { get; set; }
/// <summary>
/// 授权权限信息
/// </summary>
[Required(ErrorMessage = "RoleIdList不能为空")]
public List<long> RoleIdList { get; set; }
}

View File

@@ -1,37 +1,35 @@
//------------------------------------------------------------------------------
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 报警事件类型
/// 选择用户输出参数
/// </summary>
public enum EventTypeEnum
public class UserSelectorOutput
{
/// <summary>
/// 准备报警
/// 账号
/// </summary>
Prepare,
public string Account { get; set; }
/// <summary>
/// 报警产生
/// Id
/// </summary>
Alarm,
public long Id { get; set; }
/// <summary>
/// 报警确认
/// 姓名
/// </summary>
Confirm,
/// <summary>
/// 报警恢复
/// </summary>
Finish,
public string Name { get; set; }
}

View File

@@ -0,0 +1,126 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DependencyInjection;
using ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 用户服务
/// </summary>
public interface ISysUserService : ITransient
{
/// <summary>
/// 添加用户
/// </summary>
/// <param name="input">添加参数</param>
/// <returns></returns>
Task AddAsync(UserAddInput input);
/// <summary>
/// 删除用户
/// </summary>
/// <param name="input">Id列表</param>
/// <returns></returns>
Task DeleteAsync(params long[] input);
/// <summary>
/// 从cache中删除用户信息
/// </summary>
/// <param name="ids">用户ID列表</param>
void DeleteUserFromCache(params long[] ids);
/// <summary>
/// 禁用用户
/// </summary>
/// <param name="input">用户Id</param>
/// <returns></returns>
Task DisableUserAsync(long input);
/// <summary>
/// 编辑
/// </summary>
/// <param name="input">编辑参数</param>
/// <returns></returns>
Task EditAsync(UserEditInput input);
/// <summary>
/// 启用用户
/// </summary>
/// <param name="input">用户Id</param>
/// <returns></returns>
Task EnableUserAsync(long input);
/// <summary>
/// 根据用户ID获取按钮ID集合
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
Task<List<string>> GetButtonCodeListAsync(long userId);
/// <summary>
///根据用户账号获取用户ID
/// </summary>
/// <param name="account">用户账号</param>
/// <returns></returns>
Task<long> GetIdByAccountAsync(string account);
/// <summary>
/// 根据账号获取用户信息
/// </summary>
/// <param name="account">用户名</param>
/// <returns>用户信息</returns>
Task<SysUser> GetUserByAccountAsync(string account);
/// <summary>
/// 根据ID获取用户信息
/// </summary>
/// <param name="Id">用户ID</param>
/// <returns>用户信息</returns>
Task<SysUser> GetUserByIdAsync(long Id);
/// <summary>
/// 给用户授权角色
/// </summary>
/// <param name="input">授权参数</param>
/// <returns></returns>
Task GrantRoleAsync(UserGrantRoleInput input);
/// <summary>
/// 获取用户拥有角色
/// </summary>
/// <param name="input">用户ID</param>
/// <returns></returns>
Task<List<long>> OwnRoleAsync(BaseIdInput input);
/// <summary>
/// 用户分页查询
/// </summary>
/// <param name="input">查询参数</param>
/// <returns>用户分页列表</returns>
Task<SqlSugarPagedList<SysUser>> PageAsync(UserPageInput input);
/// <summary>
/// 重置密码
/// </summary>
/// <param name="input">用户Id</param>
/// <returns></returns>
Task ResetPasswordAsync(long input);
/// <summary>
/// 用户选择器
/// </summary>
/// <returns></returns>
Task<List<UserSelectorOutput>> UserSelectorAsync(string searchKey);
}

View File

@@ -0,0 +1,418 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DataEncryption;
using Furion.DependencyInjection;
using Furion.FriendlyException;
using Mapster;
using SqlSugar;
using ThingsGateway.Admin.Core;
using ThingsGateway.Admin.Core.JsonExtensions;
namespace ThingsGateway.Admin.Application;
/// <inheritdoc cref="ISysUserService"/>
[Injection(Proxy = typeof(OperDispatchProxy))]
public class SysUserService : DbRepository<SysUser>, ISysUserService
{
private readonly IConfigService _configService;
private readonly IRelationService _relationService;
private readonly IResourceService _resourceService;
private readonly IRoleService _roleService;
private readonly IVerificatService _verificatService;
/// <inheritdoc cref="ISysUserService"/>
public SysUserService(
IRelationService relationService,
IResourceService resourceService,
IVerificatService verificatService,
IRoleService roleService,
IConfigService configService)
{
_relationService = relationService;
_resourceService = resourceService;
_roleService = roleService;
_configService = configService;
_verificatService = verificatService;
}
/// <inheritdoc/>
[OperDesc("添加用户")]
public async Task AddAsync(UserAddInput input)
{
await CheckInputAsync(input);//检查参数
var account_Id = await GetIdByAccountAsync(input.Account);
if (account_Id > 0)
throw Oops.Bah($"存在重复的账号:{input.Account}");
var sysUser = input.Adapt<SysUser>();//实体转换
//获取默认密码
sysUser.Password = await GetDefaultPassWord();//设置密码
sysUser.UserEnable = true;//默认状态
await InsertAsync(sysUser);//添加数据
}
/// <inheritdoc/>
[OperDesc("删除用户")]
public async Task DeleteAsync(params long[] ids)
{
//获取所有ID
if (ids.Length > 0)
{
var containsSuperAdmin = await IsAnyAsync(it => it.Account == RoleConst.SuperAdmin && ids.Contains(it.Id));//判断是否有超管
if (containsSuperAdmin)
throw Oops.Bah($"不可删除系统内置超管用户");
if (ids.Contains(UserManager.UserId))
throw Oops.Bah($"不可删除自己");
var result = await DeleteByIdsAsync(ids.Cast<object>().ToArray());
if (result)
{
//从列表中删除
foreach (var id in ids)
{
await _verificatService.SetVerificatIdAsync(id, new());
}
DeleteUserFromCache(ids);
}
}
}
/// <inheritdoc />
public void DeleteUserFromCache(params long[] ids)
{
List<SysUser> sysUsers = new();
foreach (var item in ids)
{
var user = CacheStatic.Cache.Get<SysUser>(CacheConst.CACHE_SYSUSER + item, false);//获取用户列表
sysUsers.Add(user);
//删除账号
CacheStatic.Cache.Remove(CacheConst.CACHE_SYSUSER + item);
}
sysUsers = sysUsers.Where(it => it != null).ToList();//过滤掉不存在的
if (sysUsers.Count > 0)
{
var accounts = sysUsers.Select(it => it.Account).ToArray();//账号集合
foreach (var item in accounts)
{
//删除账号
CacheStatic.Cache.Remove(CacheConst.CAHCE_SYSUSERACCOUNT + item);
}
}
}
/// <inheritdoc/>
[OperDesc("禁用用户")]
public async Task DisableUserAsync(long input)
{
var sysUser = await GetUserByIdAsync(input);//获取用户信息
if (sysUser != null)
{
var isSuperAdmin = sysUser.Account == RoleConst.SuperAdmin;//判断是否有超管
if (isSuperAdmin)
throw Oops.Bah($"不可禁用系统内置超管用户账号");
CheckSelf(input, AdminConst.Disable);//判断是不是自己
//设置状态为禁用
if (await UpdateAsync(it => new SysUser { UserEnable = false }, it => it.Id == input))
{
//从列表中删除
await _verificatService.SetVerificatIdAsync(input, new());
DeleteUserFromCache(input);//从cache删除用户信息
}
}
}
/// <inheritdoc/>
[OperDesc("编辑用户")]
public async Task EditAsync(UserEditInput input)
{
await CheckInputAsync(input);//检查参数
var exist = await GetUserByIdAsync(input.Id);//获取用户信息
if (exist != null)
{
var isSuperAdmin = exist.Account == RoleConst.SuperAdmin;//判断是否有超管
if (isSuperAdmin && !UserManager.IsSuperAdmin)
throw Oops.Bah($"不可修改系统内置超管用户账号");
var sysUser = input.Adapt<SysUser>();//实体转换
if (await Context.Updateable(sysUser).IgnoreColumns(it =>
new
{
//忽略更新字段
it.Password,
it.LastLoginDevice,
it.LastLoginIp,
it.LastLoginTime,
it.LatestLoginDevice,
it.LatestLoginIp,
it.LatestLoginTime
}).ExecuteCommandAsync() > 0)//修改数据
DeleteUserFromCache(sysUser.Id);//用户缓存到cache
}
}
/// <inheritdoc/>
[OperDesc("启用用户")]
public async Task EnableUserAsync(long input)
{
CheckSelf(input, AdminConst.Enable);//判断是不是自己
//设置状态为启用
if (await UpdateAsync(it => new SysUser { UserEnable = true }, it => it.Id == input))
DeleteUserFromCache(input);//从cache删除用户信息
}
/// <inheritdoc/>
public async Task<List<string>> GetButtonCodeListAsync(long userId)
{
List<string> buttonCodeList = new();//按钮ID集合
//获取关系集合
var roleList = await _relationService.GetRelationListByObjectIdAndCategoryAsync(userId, CateGoryConst.Relation_SYS_USER_HAS_ROLE);
var roleIdList = roleList.Select(x => x.TargetId.ToLong()).ToList();//角色ID列表
if (roleIdList.Count > 0)//如果该用户有角色
{
List<long> buttonIdList = new();//按钮ID集合
var resourceList = await _relationService.GetRelationListByObjectIdListAndCategoryAsync(roleIdList, CateGoryConst.Relation_SYS_ROLE_HAS_RESOURCE);//获取资源集合
resourceList.ForEach(it =>
{
if (!string.IsNullOrEmpty(it.ExtJson)) buttonIdList.AddRange(it.ExtJson.ToJsonWithT<RelationRoleResuorce>().ButtonInfo);//如果有按钮权限将按钮ID放到buttonIdList
});
if (buttonIdList.Count > 0)
{
buttonCodeList = await _resourceService.GetCodeByIdsAsync(buttonIdList, ResourceCategoryEnum.BUTTON);
}
}
return buttonCodeList;
}
/// <inheritdoc/>
public async Task<long> GetIdByAccountAsync(string account)
{
//先从Cache拿
var userId = CacheStatic.Cache.Get<long>(CacheConst.CAHCE_SYSUSERACCOUNT + account, false);
if (userId == 0)
{
//单查获取用户账号对应ID
userId = await GetFirstAsync(it => it.Account == account, it => it.Id);
if (userId != 0)
{
//插入Cache
CacheStatic.Cache.Set(CacheConst.CAHCE_SYSUSERACCOUNT + account, userId, false);
}
}
return userId;
}
/// <inheritdoc/>
public async Task<List<string>> GetPermissionListByUserIdAsync(long userId)
{
var permissions = new List<string>();//权限集合
var roleIdList = await _relationService.GetRelationListByObjectIdAndCategoryAsync(userId, CateGoryConst.Relation_SYS_USER_HAS_ROLE);//根据用户ID获取角色ID
if (roleIdList.Count > 0)//如果角色ID不为空
{
//获取角色权限信息
var sysRelations = await _relationService.GetRelationListByObjectIdListAndCategoryAsync(roleIdList.Select(it => it.TargetId.ToLong()).ToList(), CateGoryConst.Relation_SYS_ROLE_HAS_PERMISSION);
var relationGroup = sysRelations.GroupBy(it => it.TargetId).ToList();//根据目标ID,也就是接口名分组,因为存在一个用户多个角色
//遍历分组
relationGroup.ForEach(it =>
{
HashSet<string> scopeSet = new();//定义不可重复列表
var relationList = it.ToList();//关系列表
relationList.ForEach(it =>
{
var rolePermission = it.ExtJson.ToJsonWithT<RelationRolePermission>();
scopeSet.Add(rolePermission.ApiUrl);
});
permissions.AddRange(scopeSet);//将改URL的权限集合加入权限集合列表
});
}
return permissions;
}
/// <inheritdoc/>
public async Task<SysUser> GetUserByAccountAsync(string account)
{
var userId = await GetIdByAccountAsync(account);//获取用户ID
if (userId > 0)
{
var sysUser = await GetUserByIdAsync(userId);//获取用户信息
if (sysUser.Account == account)//这里做了比较用来限制大小写
return sysUser;
else
return null;
}
else
{
return null;
}
}
/// <inheritdoc/>
public async Task<SysUser> GetUserByIdAsync(long Id)
{
//先从Cache拿需要获取新的对象避免操作导致缓存中对象改变
var sysUser = CacheStatic.Cache.Get<SysUser>(CacheConst.CACHE_SYSUSER + Id.ToString(), true);
if (sysUser == null)
{
sysUser = await Context.Queryable<SysUser>()
.Where(u => u.Id == Id)
.FirstAsync();
if (sysUser != null)
{
//获取按钮码
var buttonCodeList = await GetButtonCodeListAsync(sysUser.Id);
//获取角色码
var roleCodeList = await _roleService.GetRoleListByUserIdAsync(sysUser.Id);
//获取权限码
var permissionCodeList = await GetPermissionListByUserIdAsync(sysUser.Id);
//权限码赋值
sysUser.ButtonCodeList = buttonCodeList;
sysUser.RoleCodeList = roleCodeList.Select(it => it.Code).ToList();
sysUser.RoleIdList = roleCodeList.Select(it => it.Id).ToList();
sysUser.PermissionCodeList = permissionCodeList;
//插入Cache
CacheStatic.Cache.Set(CacheConst.CACHE_SYSUSER + sysUser.Id.ToString(), sysUser, true);
}
}
return sysUser;
}
/// <inheritdoc />
[OperDesc("用户授权")]
public async Task GrantRoleAsync(UserGrantRoleInput input)
{
var sysUser = await GetUserByIdAsync(input.Id);//获取用户信息
if (sysUser != null)
{
var isSuperAdmin = sysUser.Account == RoleConst.SuperAdmin;//判断是否有超管
if (isSuperAdmin)
throw Oops.Bah($"不能给超管分配角色");
CheckSelf(input.Id, AdminConst.GrantRole);//判断是不是自己
//给用户赋角色
await _relationService.SaveRelationBatchAsync(CateGoryConst.Relation_SYS_USER_HAS_ROLE, input.Id, input.RoleIdList.Select(it => it.ToString()).ToList(), null, true);
DeleteUserFromCache(input.Id);//从cache删除用户信息
}
}
/// <inheritdoc/>
public async Task<List<long>> OwnRoleAsync(BaseIdInput input)
{
var relations = await _relationService.GetRelationListByObjectIdAndCategoryAsync(input.Id, CateGoryConst.Relation_SYS_USER_HAS_ROLE);
return relations.Select(it => it.TargetId.ToLong()).ToList();
}
/// <inheritdoc/>
public async Task<SqlSugarPagedList<SysUser>> PageAsync(UserPageInput input)
{
var query = Context.Queryable<SysUser>()
.WhereIF(input.Expression != null, input.Expression?.ToExpression())//动态查询
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), u => u.Account.Contains(input.SearchKey))//根据关键字查询
.Mapper(u =>
{
u.Password = null;//密码清空
});
for (int i = 0; i < input.SortField.Count; i++)
{
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
}
query = query.OrderBy(it => it.SortCode);//排序
query = query.OrderBy(u => u.Id);//排序
var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页
return pageInfo;
}
/// <inheritdoc/>
[OperDesc("重置密码")]
public async Task ResetPasswordAsync(long input)
{
var password = await GetDefaultPassWord(true);//获取默认密码,这里不走Aop所以需要加密一下
//重置密码
if (await UpdateAsync(it => new SysUser { Password = password }, it => it.Id == input))
{
//从列表中删除
await _verificatService.SetVerificatIdAsync(input, new());
DeleteUserFromCache(input);//从cache删除用户信息
}
}
/// <inheritdoc/>
public async Task<List<UserSelectorOutput>> UserSelectorAsync(string searchKey)
{
var result = await Context.Queryable<SysUser>()
.WhereIF(!string.IsNullOrEmpty(searchKey), it => it.Account.Contains(searchKey))//根据关键字查询
.Select<UserSelectorOutput>()//映射成SysUserSelectorOutput
.ToListAsync();
return result;
}
#region
/// <summary>
/// 检查输入参数
/// </summary>
/// <param name="sysUser"></param>
private async Task CheckInputAsync(SysUser sysUser)
{
//判断账号重复,直接从cache拿
var account_Id = await GetIdByAccountAsync(sysUser.Account);
if (account_Id > 0 && account_Id != sysUser.Id)
throw Oops.Bah($"存在重复的账号:{sysUser.Account}");
//如果手机号不是空
if (!string.IsNullOrEmpty(sysUser.Phone))
{
if (!sysUser.Phone.MatchPhoneNumber())//验证手机格式
throw Oops.Bah($"手机号码:{sysUser.Phone} 格式错误");
sysUser.Phone = DESCEncryption.Encrypt(sysUser.Phone, DESCKeyConst.DESCKey);
}
//如果邮箱不是空
if (!string.IsNullOrEmpty(sysUser.Email))
{
var ismatch = sysUser.Email.MatchEmail();//验证邮箱格式
if (!ismatch)
throw Oops.Bah($"邮箱:{sysUser.Email} 格式错误");
if (await IsAnyAsync(it => it.Email == sysUser.Email && it.Id != sysUser.Id))
throw Oops.Bah($"存在重复的邮箱:{sysUser.Email}");
}
}
/// <summary>
/// 检查是否为自己
/// </summary>
private void CheckSelf(long id, string operate)
{
if (id == UserManager.UserId)//如果是自己
{
throw Oops.Bah($"禁止{operate}自己");
}
}
/// <summary>
/// 获取默认密码
/// </summary>
/// <returns></returns>
private async Task<string> GetDefaultPassWord(bool isSm4 = false)
{
//获取默认密码
var defaultPassword = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_PASSWORD)).ConfigValue;
return isSm4 ? DESCEncryption.Encrypt(defaultPassword, DESCKeyConst.DESCKey) : defaultPassword;//判断是否需要加密
}
#endregion
}

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