Compare commits

...

278 Commits

Author SHA1 Message Date
Kimdiego2098
fef6259d09 release: 6.0.0.9 2024-04-25 18:07:00 +08:00
Kimdiego2098
e24f19e88b docs: 更新文档 2024-04-25 18:06:03 +08:00
Kimdiego2098
cc6de1e71c build: 更新肉夹馍nuget版本 2024-04-25 18:00:19 +08:00
Kimdiego2098
b0240a1c95 refactor: appsettings添加urls配置例子 2024-04-25 17:54:43 +08:00
Kimdiego2098
8acdec333b refactor: 控制台字符logo更新 2024-04-25 17:53:56 +08:00
Kimdiego2098
eafcbb81f9 feat: DEBUG启动自动填充账密 2024-04-25 17:52:44 +08:00
Kimdiego2098
4b083463c8 refactor: gateway命名空间 2024-04-25 15:39:00 +08:00
Kimdiego2098
78e834e04f build: 修改插件类库拷贝脚本 2024-04-25 15:13:26 +08:00
Kimdiego2098
2ed1893620 feat: 添加Description方法 2024-04-25 15:12:36 +08:00
Kimdiego2098
35f188a7dd fix: CSScript using namespace无效 2024-04-25 09:47:46 +08:00
Kimdiego2098
07f577a9dd release: 6.0.0.8 2024-04-25 09:26:08 +08:00
Kimdiego2098
25c72b50c0 build: 类库net版本降至net6 2024-04-25 09:25:47 +08:00
Kimdiego2098
e07346ebea other: update readme 2024-04-24 15:00:07 +08:00
Diego2098
699b466291 other: git忽略文件 2024-04-23 21:44:59 +08:00
Diego2098
f0f24ff96f release: 6.0.0.7 2024-04-23 21:21:58 +08:00
Diego2098
1113997c71 fix: 修复类似400001.0位格式读取打包错误 2024-04-23 21:21:40 +08:00
Kimdiego2098
487fb6e5c9 release: 6.0.0.6 2024-04-23 18:34:35 +08:00
Kimdiego2098
62908a323c fix: v6版本modbusRtu bodyLength错误 2024-04-23 18:33:41 +08:00
Kimdiego2098
021131271a other: modify Pro ProjectReference Include 2024-04-23 18:08:21 +08:00
Kimdiego2098
c20fa4dbd2 fix: VersionService取消Assembly.Location用法 2024-04-23 16:54:38 +08:00
Kimdiego2098
b1826678da release: 6.0.0.5 2024-04-23 12:12:17 +08:00
Kimdiego2098
1630338f4e refactor: 修改条件编译 2024-04-23 12:11:53 +08:00
Kimdiego2098
78af3d979c fix: 菜单curd
1、创建模块后,切换模块列表不能马上显示
2、创建根菜单时,不填写路径会出错
2024-04-23 12:08:40 +08:00
Kimdiego2098
8346dfb1f5 perf: 添加ConfigureAwait(false) 2024-04-23 11:33:26 +08:00
Kimdiego2098
5ecb4e4fe4 refactor(VerificatInfoCacheService): DEBUG模式下,实时持久化登录凭据 2024-04-23 08:45:16 +08:00
Kimdiego2098
ec9ff23b23 refactor(DeviceStatusPage): 设备页面筛选下拉框null值显示为none 2024-04-23 08:37:27 +08:00
Diego2098
149e9931e7 refactor: 修改user默认模块种子数据 2024-04-23 00:02:11 +08:00
Diego2098
58b62094bd release: 6.0.0.4 2024-04-22 23:57:51 +08:00
Diego2098
d357074ad4 refactor: 修改excel导出的字段排序 2024-04-22 23:57:14 +08:00
Diego2098
d9bacc129b feat: 业务缓存设备基类添加属性(缓存文件最大长度) 2024-04-22 23:31:48 +08:00
Diego2098
28d869b099 refactor: excel导入时,按行号排序 2024-04-22 23:25:40 +08:00
Kimdiego2098
d04e87718a release: 6.0.0.3 2024-04-22 18:31:41 +08:00
Kimdiego2098
8d81bf60c9 feat(dlt645): 修改FE前导符为自定义格式 2024-04-22 18:30:47 +08:00
Kimdiego2098
4fe9094ff7 refactor: trace日志构建前设置条件 2024-04-22 17:41:22 +08:00
Kimdiego2098
4cfde45755 refactor: 修改单线程读写时的锁范围 2024-04-22 17:12:16 +08:00
Kimdiego2098
c28844dbeb refactor: 迁移读写方法到基础类库 2024-04-22 16:59:14 +08:00
Kimdiego2098
837ea7c8d6 refactor: 迁移Dtu读写方法到基础类库 2024-04-22 14:42:27 +08:00
Kimdiego2098
6c85ac8827 refactor: 补充nuget发布类库 2024-04-22 14:27:53 +08:00
Kimdiego2098
c8eb8bf35d release: 6.0.0.2 2024-04-22 14:22:37 +08:00
Kimdiego2098
0f6cbddd57 refactor: 迁移DtuPlugin到共用类库 2024-04-22 14:18:57 +08:00
Kimdiego2098
530b4755bf refactor: 底层驱动类库升级net版本,保留net462以及netstandard2.0 2024-04-22 13:58:14 +08:00
Kimdiego2098
679395b3fe refactor: modbusMaster 添加 分部定义 2024-04-22 13:56:45 +08:00
Kimdiego2098
9293442836 fix: editForm不应该直接修改缓存 2024-04-22 10:33:51 +08:00
Kimdiego2098
4841b412f3 feat: v6网关模块添加按钮权限 2024-04-22 10:08:57 +08:00
Kimdiego2098
f03575ad89 fix: 角色授权流程错误 2024-04-22 10:07:49 +08:00
Diego2098
e0e5428ebd feat: 添加业务设备离线缓存 2024-04-21 00:06:32 +08:00
Diego2098
015f7edae2 style: 表格列宽调整 2024-04-20 23:14:47 +08:00
Diego2098
d5414f5d91 !20 fix: 修复表格不对齐问题
* fix: 修复表格不对齐问题 , thanks to @Argo
2024-04-20 14:59:00 +00:00
Kimdiego2098
4bc3512d42 按钮授权初始化授权列表错误 2024-04-19 12:50:48 +08:00
Kimdiego2098
4c305994ca 添加多语言资源 2024-04-19 11:39:10 +08:00
Kimdiego2098
788e2cba04 修改变量页面默认排序 2024-04-18 16:52:19 +08:00
Kimdiego2098
b79b79970e 实时数据页面添加关键词搜索 2024-04-18 16:18:53 +08:00
Kimdiego2098
8b0f4de516 更新 2024-04-18 16:15:54 +08:00
Kimdiego2098
a17000ad6d 修改判断值变化方法 2024-04-18 16:15:53 +08:00
Kimdiego2098
c9f63c58df 修改项目文件 2024-04-18 16:01:38 +08:00
Kimdiego2098
c311b1c706 更新git文件 2024-04-18 14:36:58 +08:00
Kimdiego2098
072451afae 删除多余重写属性 2024-04-18 11:06:00 +08:00
Kimdiego2098
7349793463 更新项目文件 2024-04-18 10:21:31 +08:00
Kimdiego2098
c0144eab86 调整项目文件 2024-04-18 10:18:09 +08:00
Kimdiego2098
ba79b25944 VerificatInfo删除不必要的属性 2024-04-17 18:56:20 +08:00
Kimdiego2098
3fb34b12a0 更新部署文件 2024-04-17 15:00:09 +08:00
Kimdiego2098
b6c17e76c2 更新部署文件 2024-04-17 14:57:01 +08:00
Kimdiego2098
1201f20d79 删除不必要的文件 2024-04-17 12:30:44 +08:00
Kimdiego2098
98ae8e692e 添加文档 2024-04-17 12:20:01 +08:00
Kimdiego2098
8342dbff4e 更新6.0版本 2024-04-17 12:18:00 +08:00
Kimdiego2098
a22aa0f9a7 Merge branch 'dev' into temp 2024-04-17 12:15:23 +08:00
Kimdiego2098
b49d221ec1 准备合并dev分支 2024-04-17 12:14:05 +08:00
Kimdiego2098
f1be315147 更新插件 2024-04-17 11:32:36 +08:00
Kimdiego2098
8aaac2a3d4 更新插件UI 2024-04-17 10:55:46 +08:00
Kimdiego2098
bd07d30e47 更新插件 2024-04-17 10:51:42 +08:00
Kimdiego2098
54e103f00f 更新插件 2024-04-17 10:32:31 +08:00
Kimdiego2098
5404d8f7ab 更新插件 2024-04-17 10:21:45 +08:00
Diego2098
1364e02c67 更新插件 2024-04-17 00:42:25 +08:00
Diego2098
b4818e2f9a 修复 变量页面 自动刷新时,每页条数会恢复初始值 的问题 2024-04-16 22:54:05 +08:00
Diego2098
734cf57d4a 更新插件 2024-04-16 21:22:49 +08:00
Diego2098
9236610ec1 更新插件 2024-04-16 21:12:38 +08:00
Diego2098
c33828a5d9 更新插件 2024-04-16 21:11:42 +08:00
Diego2098
faadb03e46 !18 更新插件
Merge pull request !18 from BAD MAN/dev_20240416
2024-04-16 10:11:54 +00:00
allrightsreserved
3a0c4c51d2 Merge branch 'dev' of https://gitee.com/dancingqueen/ThingsGateway into dev 2024-04-16 16:02:40 +08:00
Kimdiego2098
6c28c2b91e 更新OPCUA OPCDA 2024-04-16 16:02:26 +08:00
Kimdiego2098
5226df6194 更新readme 2024-04-16 14:45:15 +08:00
Kimdiego2098
1ad23a3cbb 更新依赖 2024-04-16 14:29:13 +08:00
Kimdiego2098
c4149ca304 修改变量服务 导出方法名称 2024-04-16 14:26:19 +08:00
Kimdiego2098
68b02fe950 优化导入导出,支持属性验证 2024-04-16 14:02:41 +08:00
Kimdiego2098
d9408523a5 更新插件 2024-04-16 11:12:28 +08:00
Diego2098
40ad0f063a 更新版本 2024-04-15 23:31:57 +08:00
Diego2098
40d85e8926 恢复部分代码:tcpclientbase;IDBHistoryAlarm;sqlhisalarm 2024-04-15 23:31:43 +08:00
Diego2098
e655f719e6 !16 为历史报警增加外部访问接口,linux下隐藏 OPCDA 调试选项,TgTcpClientBase.cs 增加try catch,防止出现设备无法连接时visualstudio 抛出异常“在一个非嵌套字上尝试了一个操作”
Merge pull request !16 from BAD MAN/master
2024-04-15 15:26:40 +00:00
BAD MAN
ede0ea02c5 Merge branch 'master' of https://gitee.com/dancingqueen/ThingsGateway 2024-04-15 22:46:37 +08:00
Diego2098
e579575b21 为 history alarm 增加 GetDBHistoryAlarmsAsync 和 GetDBHistoryAlarmPagesAsync 接口,方便外部访问历史报警;增加try catch,防止出现设备无法连接时visualstudio 抛出异常“在一个非嵌套字上尝试了一个操作”;linux 环境下 OPCDA 不显示 2024-04-15 22:46:35 +08:00
Kimdiego2098
06ffcc9fdf 更新插件 2024-04-15 18:14:23 +08:00
Kimdiego2098
774d03e510 更新插件 2024-04-15 17:39:02 +08:00
Kimdiego2098
adcac9dfe6 添加任务最大数量限制 2024-04-15 16:50:06 +08:00
Kimdiego2098
399e14c70c 去除头像种子数据 2024-04-15 14:25:35 +08:00
Kimdiego2098
ec8b51033b signalr dispose 忽略exception 2024-04-15 14:23:29 +08:00
Kimdiego2098
ca5d167b6a 更新插件 2024-04-15 13:57:05 +08:00
Kimdiego2098
fcd1b001d1 更新readme 2024-04-15 13:34:32 +08:00
Kimdiego2098
1cabbefe04 更新插件 2024-04-15 13:31:28 +08:00
Kimdiego2098
ecefe22c95 更新插件 2024-04-15 13:06:43 +08:00
Kimdiego2098
64db77b9a2 更新插件 2024-04-15 13:03:32 +08:00
Kimdiego2098
19d7360ad5 更新插件 2024-04-15 13:01:46 +08:00
Kimdiego2098
2f014cd827 更新插件 2024-04-15 12:46:17 +08:00
Kimdiego2098
7fb84205d9 更新插件 2024-04-15 12:38:07 +08:00
Kimdiego2098
47e1127c5f 更新插件 2024-04-15 12:21:16 +08:00
Kimdiego2098
e036b59306 更新插件 2024-04-15 12:04:34 +08:00
Kimdiego2098
f4904c3b53 添加插件 2024-04-15 11:31:13 +08:00
Diego2098
3002a63ba5 更新BootstrapBlazor版本 2024-04-14 23:38:47 +08:00
Diego2098
b69717e6c3 更新版本 2024-04-14 23:37:05 +08:00
Diego2098
d07962953f 修改注释 2024-04-14 23:16:30 +08:00
Diego2098
857cf0d21e delay策略更改 2024-04-14 23:10:37 +08:00
Diego2098
5f08c2615d 修复缓存类型转换错误 2024-04-14 23:05:21 +08:00
Diego2098
e87428ef33 更新6.0版本 2024-04-14 22:51:56 +08:00
Kimdiego2098
03fd54fe70 td时序库上传时间不应作为主键 2024-04-12 16:57:09 +08:00
Kimdiego2098
086c2c8253 取消ParallelForEachAsync方法 2024-04-11 14:54:54 +08:00
Kimdiego2098
05c19a32ea s7 修改解析 返回码 顺序 2024-04-10 15:20:18 +08:00
Kimdiego2098
99d174906a 更新版本 2024-04-08 21:48:50 +08:00
Kimdiego2098
82e30a326a 修复上个版本 modbus驱动打包变量 代码错误 2024-04-08 21:46:47 +08:00
Diego2098
2963a9cdca !14 获取历史数据之前检查历史数据库是否连接成功
Merge pull request !14 from BAD MAN/master
2024-04-05 13:53:55 +00:00
BAD MAN
288da75b2b Merge branch 'master' of https://gitee.com/dancingqueen/ThingsGateway 2024-04-05 21:05:57 +08:00
Kimdiego2098
0943a496dd 获取历史数据之前检查历史数据库是否连接成功更新版本 2024-04-05 21:04:47 +08:00
Kimdiego2098
e5dd7cc2fa 强推 2024-04-03 19:01:57 +08:00
Kimdiego2098
358836ef9f 更新版本 2024-04-01 10:00:00 +08:00
Kimdiego2098
aab4fac6c5 modbus DTU模式 修复socketid不同时的变量打包方式,以及心跳注册包等不再影响正常数据收发 2024-04-01 09:59:48 +08:00
Kimdiego2098
525540b603 S7打包方法内变量名称修正 2024-03-31 11:36:56 +08:00
Kimdiego2098
b30eeb4694 mchart 更新 2024-03-30 22:03:19 +08:00
Kimdiego2098
3faf0aa2fc 更新版本 2024-03-29 23:23:28 +08:00
Kimdiego2098
fd728dec5d 更新依赖包 2024-03-29 23:23:15 +08:00
Kimdiego2098
08b14b72d4 读写表达式更换为CS-Script实现 2024-03-29 23:15:52 +08:00
Diego2098
de2e005abf 更新版本 2024-03-27 22:17:55 +08:00
Diego2098
0fc75239a6 添加PreEvaluateVariableEnable配置,读取表达式/写入表达式中可以用变量名称获取全局变量的值,因性能问题默认关闭 2024-03-27 22:15:41 +08:00
Kimdiego2098
390fe30a0d 更新版本 2024-03-26 15:02:05 +08:00
Kimdiego2098
633f49fcd2 opcda导出节点excel表时通道名称错误 2024-03-26 15:01:57 +08:00
Kimdiego2098
30c0ba93b9 修复 s7 通过.n的方式读取字节中布尔量时,如果变量地址带有分号时出现的分包错误 2024-03-25 13:28:58 +08:00
Diego2098
e935fb9621 修正数组类型判断值变化方式 2024-03-24 23:08:55 +08:00
Kimdiego2098
5ce8bb1d08 修复首页刷新线程返回时,option还未赋值成功导致报错问题 2024-03-22 12:19:55 +08:00
Kimdiego2098
1acd12980a 调试页面显示异常堆栈 2024-03-16 16:59:12 +08:00
Kimdiego2098
683235dd8a 发布构建脚本重复错误 2024-03-15 10:53:13 +08:00
Kimdiego2098
65fe183ad4 冗余配置文件丢失时不再报空指针错误 2024-03-15 09:55:34 +08:00
Kimdiego2098
f39d5d355c 更新版本 2024-03-13 10:33:15 +08:00
Kimdiego2098
eccc8e0ff0 更新sqldb,删除不需要的转换 2024-03-13 10:25:26 +08:00
Kimdiego2098
dd4d8e775c 更新版本 2024-03-11 16:39:43 +08:00
Kimdiego2098
7a7f857b2f startUp调整注入顺序 2024-03-11 16:39:00 +08:00
Diego2098
10882b7d93 规范代码 2024-03-10 14:29:33 +08:00
Diego2098
e669b81005 发布构建时,默认删除PDB文件 2024-03-10 13:05:25 +08:00
Diego2098
28a81d9539 更新版本 2024-03-10 11:47:32 +08:00
Diego2098
6bf5e4a6b8 修改设备/变量 初始时间为unix 2024-03-10 11:46:05 +08:00
Diego2098
a51eee93f4 更新版本 2024-03-09 16:20:17 +08:00
Diego2098
bece2555c2 自动刷新图标时,启用echarts组件合并模式 2024-03-09 16:13:02 +08:00
Diego2098
d98d405009 更新touchsocket依赖正式版本 2024-03-09 15:19:19 +08:00
Diego2098
8d0881632c 业务设备初始默认状态为成功 2024-03-09 13:37:17 +08:00
Diego2098
e8b81da897 sqldb按选项建表 2024-03-09 13:19:35 +08:00
Diego2098
acf4fbf750 生产环境打印错误sql 2024-03-09 13:16:20 +08:00
Diego2098
b4f1921d33 更新版本 2024-03-09 11:32:26 +08:00
Diego2098
94b4816f53 修复sqldb查询接口错误,增加历史表-变量id字段 2024-03-09 11:29:04 +08:00
Kimdiego2098
b6ddafde3e 更新版本 2024-03-08 17:31:46 +08:00
Kimdiego2098
b01036818f 整理代码 2024-03-08 16:31:33 +08:00
Kimdiego2098
ad7da1a0c3 历史存储插件增加接口,增加历史查询方法 2024-03-08 16:27:45 +08:00
Kimdiego2098
1e0818d9d9 变量导入时,新增部分按行号排序 2024-03-08 16:27:19 +08:00
Kimdiego2098
a4686f01c3 更新touchsocket依赖 2024-03-08 13:22:40 +08:00
Kimdiego2098
84d76f9aab 更新版本 2024-03-07 19:23:05 +08:00
Kimdiego2098
b90bf5eb86 Revert "回退sqlsugar版本(sqldb分表问题)"
This reverts commit fe258f3fe5.
2024-03-07 18:47:04 +08:00
Kimdiego2098
fe258f3fe5 回退sqlsugar版本(sqldb分表问题) 2024-03-07 18:43:37 +08:00
Kimdiego2098
d339494594 更新版本 2024-03-07 15:03:17 +08:00
Kimdiego2098
adfaf13055 更新版本 2024-03-07 12:21:12 +08:00
Kimdiego2098
8abfeb5923 修复 sqldb oracle11 标识符过长 2024-03-07 12:11:20 +08:00
Kimdiego2098
c1a027a771 更新版本 2024-03-06 17:40:21 +08:00
Kimdiego2098
5f3094d79b 恢复误删代码 2024-03-06 12:49:58 +08:00
Kimdiego2098
925b81aca8 更新版本 2024-03-06 12:32:08 +08:00
Kimdiego2098
c0c8437966 OpcDa添加是否使用服务端时间选项 2024-03-06 12:31:43 +08:00
Kimdiego2098
1d27b2fc4a 表达式运行添加linq扩展方法,长度限制1000 2024-03-06 10:57:11 +08:00
Kimdiego2098
5da43e7808 更新版本 2024-03-05 16:55:12 +08:00
Kimdiego2098
5edbb558ae 更新版本 2024-03-04 13:12:47 +08:00
Kimdiego2098
7347cc1df2 json解析布尔值时,特殊忽略大小写 2024-03-04 12:43:47 +08:00
Kimdiego2098
20cac11b2a 磁盘使用率逻辑,缓存删除日志修改 2024-03-04 10:59:49 +08:00
Kimdiego2098
75c35c4ff8 更新版本 2024-03-04 00:10:43 +08:00
Diego2098
9d573512d0 更新版本 2024-03-03 23:49:47 +08:00
Diego2098
7921365853 更新版本 2024-03-03 23:03:02 +08:00
Diego2098
43263fd3b9 分离Variable类库 2024-03-03 17:43:37 +08:00
Diego2098
59042a5ead 修改echart背景颜色,去除多余的字体颜色 2024-03-02 13:39:17 +08:00
Kimdiego2098
91b14de807 文件编码覆盖 2024-03-01 18:54:36 +08:00
Kimdiego2098
81fab2be08 更新依赖包 2024-03-01 13:36:20 +08:00
Kimdiego2098
165b742782 更新版本 2024-03-01 13:33:49 +08:00
Kimdiego2098
76fef9c807 优化业务插件缓存逻辑 2024-03-01 13:33:43 +08:00
Kimdiego2098
e69ea0b9dc 更新版本 2024-02-28 17:41:02 +08:00
Kimdiego2098
98d3183f2b 修复sqldb历史表动态分表名称查询失效 2024-02-28 17:40:51 +08:00
Kimdiego2098
a29390a951 更新依赖包 2024-02-28 17:13:42 +08:00
Kimdiego2098
6291ce8617 modbusSlave运行时检查恢复启动 2024-02-28 17:08:31 +08:00
Kimdiego2098
c76b1b50a0 更新版本 2024-02-28 16:12:55 +08:00
Kimdiego2098
cc45e2aec0 更新网关双冗余 2024-02-28 16:12:38 +08:00
Kimdiego2098
17efebb8e8 更新版本 2024-02-28 16:05:50 +08:00
Kimdiego2098
5c94c733ee 整理代码 2024-02-28 16:05:18 +08:00
Kimdiego2098
156b89dd9c 更新双网关冗余 2024-02-28 16:04:04 +08:00
Kimdiego2098
34ba9f67e7 更新双网关冗余,sqldb支持历史表名称更改,更改为按周分表 2024-02-28 15:53:46 +08:00
Kimdiego2098
5ddaa6b872 双网关冗余(未完成) 2024-02-27 17:50:23 +08:00
Kimdiego2098
9043fa7f56 整理 2024-02-27 16:58:01 +08:00
Kimdiego2098
4c8e487dc9 双网关冗余(未完成) 2024-02-27 13:59:12 +08:00
Kimdiego2098
d3b87179aa 双网关冗余(未完成) 2024-02-26 19:49:19 +08:00
Kimdiego2098
2166de8331 修改Windows下UDP连接被重置错误10054代码 2024-02-26 12:57:48 +08:00
Diego2098
f0bc3f001f opcda nuget包不包含symbols 2024-02-25 15:31:48 +08:00
Diego2098
50448e7085 nuget包不需要发布源生成库 2024-02-25 15:11:15 +08:00
Diego2098
cd1d42353e 更新版本 2024-02-25 15:01:06 +08:00
Diego2098
5e588bf737 添加null判断 2024-02-25 14:59:17 +08:00
Kimdiego2098
7ff777d178 更新版本 2024-02-25 14:48:23 +08:00
Kimdiego2098
861621189a 底层变量特性实体支持数组 2024-02-25 14:39:08 +08:00
Diego2098
dcc00e08fd 整理代码 2024-02-24 23:32:51 +08:00
Diego2098
a1b8a47d4b 更新示例 2024-02-24 22:43:49 +08:00
Diego2098
1fd6b5e239 更改动态变量类缓存 2024-02-24 21:45:59 +08:00
Diego2098
f91e45bf44 整理代码 2024-02-24 18:42:45 +08:00
Kimdiego2098
0675a45592 更新版本 2024-02-24 18:23:41 +08:00
Kimdiego2098
0c7c6ae451 s7协议首次CR请求时,判断方式错误 2024-02-24 18:23:10 +08:00
Diego2098
cf089e8c4c 更新版本 2024-02-24 17:19:09 +08:00
Diego2098
90928ac679 更新版本 2024-02-24 17:14:57 +08:00
Diego2098
3bbcf71784 整理代码 2024-02-24 17:12:23 +08:00
Diego2098
222734775d 整理代码 2024-02-24 16:32:47 +08:00
Diego2098
f23ee7a6e0 整理代码 2024-02-24 16:08:37 +08:00
Diego2098
5b075aa6d5 更新底层变量类,添加源代码生成方法 2024-02-24 14:39:53 +08:00
Kimdiego2098
80cd6b693e 发行版本5.0.1.0 2024-02-23 13:09:25 +08:00
Kimdiego2098
04721a12b1 发行版本5.0.1.0 2024-02-23 13:03:53 +08:00
Kimdiego2098
64e22c0e46 取消默认PublishReadyToRun 2024-02-23 13:03:46 +08:00
Kimdiego2098
d5a70c5b08 Revert "发行版本5.0.1"
This reverts commit eaac7b6bcf.
2024-02-23 13:03:12 +08:00
Kimdiego2098
eaac7b6bcf 发行版本5.0.1 2024-02-23 12:58:25 +08:00
Kimdiego2098
b062a491cd 调整代码 2024-02-23 12:36:28 +08:00
Kimdiego2098
1e868517bb 默认启用PublishReadyToRun 2024-02-23 11:02:56 +08:00
Kimdiego2098
7b2a93a2d7 更新版本 2024-02-22 18:13:10 +08:00
Kimdiego2098
f57f0447c6 完善modbus写入保持寄存器某一位,比如400001.0的功能 2024-02-22 18:12:58 +08:00
Kimdiego2098
7126ff881e 更新版本 2024-02-22 17:52:32 +08:00
Kimdiego2098
e28da4b165 modbusTcp返回错误码时没有验证头部id 2024-02-22 17:52:20 +08:00
Kimdiego2098
92d9b91f7c 更新依赖包 2024-02-22 17:31:27 +08:00
Kimdiego2098
149c4a30c0 写入表达式支持json数组 2024-02-21 14:50:21 +08:00
Kimdiego2098
84e62062ec 表达式转换支持Json 2024-02-21 13:35:41 +08:00
Kimdiego2098
dc1fb74850 更新readme 2024-02-21 11:33:00 +08:00
Kimdiego2098
00c6010789 更新readme 2024-02-21 11:07:00 +08:00
Kimdiego2098
5d35c058e0 更新readme 2024-02-21 11:05:54 +08:00
Kimdiego2098
1522a521f6 更新readme 2024-02-21 10:59:53 +08:00
Kimdiego2098
6e11b885f9 同步代码 2024-02-21 09:38:34 +08:00
Kimdiego2098
442ae6e0e8 插件初始化失败时设置线程超时 2024-02-20 12:05:02 +08:00
Kimdiego2098
6b49e83464 opcua重连周期改为10s 2024-02-19 17:09:14 +08:00
Kimdiego2098
f1ecf13fe1 更新版本 2024-02-19 14:31:25 +08:00
Kimdiego2098
83d1c8582b 整理signalR代码 2024-02-19 14:31:14 +08:00
Kimdiego2098
179f6cd454 程序启动时清除在线用户统计 2024-02-19 13:42:28 +08:00
Kimdiego2098
91b1474ff0 更新文档 2024-02-19 09:44:55 +08:00
Kimdiego2098
15aabc88a1 更新文档 2024-02-18 18:26:46 +08:00
Kimdiego2098
afc0d3017d 修复等待池超限后没有恢复初始值的问题 2024-02-18 17:43:44 +08:00
Kimdiego2098
9e0b1dc8aa 更新文档 2024-02-18 16:05:18 +08:00
Kimdiego2098
55b482fd26 更新版本 2024-02-18 15:23:32 +08:00
Kimdiego2098
4f9c9a6566 发送方法更改为异步 2024-02-18 15:21:57 +08:00
Kimdiego2098
4725120ee9 添加历史cpu曲线过期时间 2024-02-18 15:04:37 +08:00
Kimdiego2098
a288f50fbb 演示环境默认填写帐户密码 2024-02-18 14:19:37 +08:00
Kimdiego2098
09cf2560a4 更新版本 2024-02-18 12:13:03 +08:00
Kimdiego2098
16353de7b1 修复LiteDB中的表达式缓存导致的内存泄露 2024-02-18 12:12:25 +08:00
Kimdiego2098
eb5834cb5c 更新版本 2024-02-18 10:00:40 +08:00
Kimdiego2098
3ef5736aed 更新依赖 2024-02-18 09:59:11 +08:00
Kimdiego2098
d2e7b77d16 Demo环境不再允许修改个人密码 2024-02-18 09:58:53 +08:00
Kimdiego2098
8f794bce75 更新文档 2024-02-18 09:35:24 +08:00
Kimdiego2098
c33196a50a 更新版本 2024-02-04 09:12:43 +08:00
Kimdiego2098
6e8b5b431f 更改种子数据解析方法 2024-02-04 09:09:29 +08:00
Kimdiego2098
8ca3ecf17f 更新版本 2024-02-02 15:44:12 +08:00
Kimdiego2098
91f7db59ea 更新依赖包 2024-02-02 15:44:02 +08:00
Kimdiego2098
a0b4501352 更新版本 2024-02-02 15:40:08 +08:00
Kimdiego2098
345047820a 更新依赖 2024-02-02 15:39:54 +08:00
Kimdiego2098
ac3525a953 更新版本 2024-02-02 15:15:32 +08:00
Kimdiego2098
c8c3f5b134 更新构建脚本 2024-02-02 15:14:37 +08:00
Kimdiego2098
51319c0718 mqttClient每次发布都作为遗嘱消息 2024-02-02 14:38:24 +08:00
Kimdiego2098
43399b8b47 mqttClient连接失败时缓存数据 2024-02-02 14:36:49 +08:00
Kimdiego2098
a6596042b7 修复上传业务缓存删除逻辑 2024-02-02 14:26:00 +08:00
Kimdiego2098
23ae85fc9c Revert "修复上传业务缓存删除逻辑"
This reverts commit 2da54862f1.
2024-02-02 14:25:21 +08:00
Kimdiego2098
2da54862f1 修复上传业务缓存删除逻辑 2024-02-02 14:24:49 +08:00
Kimdiego2098
f272fb0559 更新版本 2024-02-02 13:00:04 +08:00
Kimdiego2098
bd04e33586 底层通道设备列表更换为线程安全列表 2024-02-02 12:29:19 +08:00
Kimdiego2098
b09b9752ca mqttServer修复客户端连接时,发送初始数据分组错误 2024-02-02 12:16:43 +08:00
Kimdiego2098
a810a48158 更新dockerfile 2024-02-02 08:40:10 +08:00
Kimdiego2098
b4f5792aa8 串口流BeginRead更换为ReadAsync 2024-02-01 16:50:01 +08:00
Kimdiego2098
fdf0330b4f 更新版本 2024-02-01 12:20:11 +08:00
Kimdiego2098
ca73743082 调整服务启动顺序 2024-02-01 12:19:44 +08:00
Kimdiego2098
df0cde2cfd 定长字符串写入时,长度不足也覆盖剩余字节 2024-01-30 17:37:44 +08:00
Kimdiego2098
5a8421e807 更新版本 2024-01-30 14:33:57 +08:00
Kimdiego2098
025ac95d81 修复数据转换无默认构造导致的反序列化错误 2024-01-30 14:27:33 +08:00
Kimdiego2098
71b5824fdc 添加重启事件 2024-01-30 13:52:17 +08:00
1489 changed files with 75457 additions and 69058 deletions

4
.gitignore vendored
View File

@@ -366,6 +366,6 @@ FodyWeavers.xsd
/src/*Pro*
/src/*pro*
/src/*pro*/
/src/ThingsGateway.Web.Entry/.config/
/doc/.*
/src/ThingsGateway.Server/.config/
/src/nuget.exe

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. Redistribution. You may reproduce and distribute copies of the
4. Cachetribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:

View File

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

## Introduction

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

## Documentation

[ThingsGateway Documentation](https://diego2098.gitee.io/thingsgateway-docs/).

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

### Plugin List

#### Data Collection Plugins
## 介绍
**NetCore** 跨平台边缘采集网关(工业设备采集)
**ThingsGateway** 存储库同时提供 [**设备采集驱动**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22)
**ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 **ThingsGateway - Admin**
## 文档
[ThingsGateway](https://diego2098.gitee.io/thingsgateway-docs/) 文档。
## 协议
[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 |
|-------|-------|
| Modbus | Supports Rtu/Tcp message formats, with Serial/Tcp/Udp links |
| SiemensS7 | Siemens PLC S7 series |
| Dlt6452007 | Supports Serial/Tcp/Udp links |
| OpcDaMaster | Compiled for 64-bit |
| OpcUaMaster | Supports certificate login, object extension, Json read/write |
#### Business Plugins
| Plugin Name | Remarks |
|-------|-------|
| ModbusSlave | Supports Rtu/Tcp message formats, with Serial/Tcp/Udp links, supports Rpc reverse writing |
| OpcUaServer | OpcUa server, supports Rpc reverse writing |
| MqttClient | Mqtt client, supports Rpc reverse writing, script-customizable upload content |
| MqttServer | Mqtt server, supports WebSocket, supports Rpc reverse writing, script-customizable upload content |
| KafkaProducer | Script-customizable upload content |
| RabbitMQProducer | Script-customizable upload content |
| SqlDB | Relational database storage, supports historical storage and real-time data updates |
| SqlHisAlarm | Alarm historical data relational database storage |
| TDengineDB | Time-series database storage |
| QuestDB | Time-series database storage |

## License

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

## Demo

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

Account: **SuperAdmin**

Password: **111111**

**In the upper-right corner, switch to the IoT Gateway module in the personal popup box**

## Sponsorship

[Sponsorship Approach](https://diego2098.gitee.io/thingsgateway-docs/docs/1000)

## Community

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

## Pro Plugins

[Plugin List](https://diego2098.gitee.io/thingsgateway-docs/docs/1001)

66
README.zh-CN.md Normal file
View File

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

View File

@@ -61,7 +61,7 @@ import Highlight from '@site/src/components/Highlight.js';
```
`400001`是PLC Modbus的地址表示方式代表保持寄存器地址0### 1、从站地址
`400001`是PLC Modbus的地址表示方式代表保持寄存器地址0
- 基本地址

View File

@@ -19,7 +19,7 @@ import Highlight from '@site/src/components/Highlight.js';
## 一、说明
**ThingsGateway** 基于NET6、7、8,默认开发IDE为VS2022(**17.8版本以上**),安装VS时需勾选ASP.NET类别.
**ThingsGateway** 基于NET6、8,默认开发IDE为VS2022(**17.8版本以上**),安装VS时需勾选ASP.NET类别.
<img src={require("@site/static/img/docs/vs2022install.png").default} />
@@ -42,7 +42,7 @@ import Highlight from '@site/src/components/Highlight.js';
<img src={require("@site/static/img/docs/生成解决方案.png").default} />
2、 设置ThingsGateway.Web.Entry为启动项目,开始调试或开始执行
2、 设置ThingsGateway.Server为启动项目,开始调试或开始执行
<img src={require("@site/static/img/docs/设置启动项目.png").default} />
<img src={require("@site/static/img/docs/开始执行.png").default} />
@@ -54,7 +54,7 @@ import Highlight from '@site/src/components/Highlight.js';
:::tip 提示
测试环境下,账密会自动填充为超级管理员账号,默认账户:**superAdmin**,密码:**111111**
测试环境下,账密会自动填充为超级管理员账号,默认账户:**SuperAdmin**,密码:**111111**
:::

31
doc/docs/30001.mdx Normal file
View File

@@ -0,0 +1,31 @@
---
id: 30001
title: 串口
---
import useBaseUrl from "@docusaurus/useBaseUrl";
import Tag from "@site/src/components/Tag.js";
import Highlight from '@site/src/components/Highlight.js';
### 前言
串口使用一个称为串行通信协议的协议来管理数据传输。串行通信协议在数据传输期间控制数据流,包括起始位、数据位、波特率、校验位和停止位等。
下面每个名称做一个简单的解释。
``波特率``是指串口通信中每秒传输的二进制位数
``起始位``是1位时间、值为0的位。
``数据位``通常可能为1位、1.5位、2位时间。
``校验位``是1位时间根据校验方式确定验证值比如奇校验时计算数据位中的值为1的个数如果是奇数则为1否则应为0
``停止位``通常可能为1位、1.5位、2位时间。停止位是值为1的位。停止位代表着数据传输结束。
线路路空闲或者数据传输结束时值总是1。
对于正逻辑的TTL电平值为1是高电平对于负逻辑(如RS-232电平)则相反。
单看文字很难理解具体含义,所以我们看图说话,下面这张串口示波器图清晰的表现出二进制的传输原理
<img src={require("@site/static/img/docs/串口1.png").default} />
如上图可以看出2进制是 起始``1``、数据(右到左)``01010101``、停止``0`` ,那么解析出来的数据就是``0x55``
看到这里相信大家都对串口知识有了一定的理解这些基础知识对于c#工程师看似没有必要,但可以让你的实际调试开发工作更加得心应手

View File

@@ -274,6 +274,20 @@ module.exports = {
},
]
},
{
type: "category",
label: "协议开发教程",
items: [
{
type: "doc",
label: "基础知识",
id: "30001",
}
]
},
{
type: "category",
label: "技术支持/合作",

View File

@@ -83,7 +83,7 @@ function Banner() {
<div className="ThingsGateway-get-start-btn">
<Link className="ThingsGateway-get-start" to={useBaseUrl("docs/")}>
入门指南
<span className="ThingsGateway-version">v5.0</span>
<span className="ThingsGateway-version">v6.0</span>
</Link>
</div>
</div>
@@ -114,7 +114,7 @@ function Gitee() {
className={"ThingsGateway-log-jiao" + (isDarkTheme ? " dark" : "")}
></div>
<div className="ThingsGateway-log-number">
<div style={{ color: "#723cff" }}>600 +</div>
<div style={{ color: "#723cff" }}>700 +</div>
<span className={isDarkTheme ? " dark" : ""}>Stars</span>
</div>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 152 KiB

BIN
doc/static/img/docs/串口1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,25 +1,247 @@
[*.cs]
root = true
# IDE0290: 使用主构造函数
dotnet_diagnostic.IDE0290.severity = none
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
###############################
# Core EditorConfig Options #
###############################
# IDE0028: 简化集合初始化
dotnet_diagnostic.IDE0028.severity = none
# All files
[*]
indent_style = space
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
spelling_exclusion_path = .\exclusion.dic
# CA1822: 将成员标记为 static
dotnet_diagnostic.CA1822.severity = none
# Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers = false
csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
csharp_style_var_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True
dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field
dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef
dotnet_naming_rule.unity_serialized_field_rule.severity = warning
dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style
dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols
dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = *
dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds =
dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field
dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
dotnet_style_qualification_for_event = false:suggestion
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
# IDE0052: 删除未读的私有成员
dotnet_diagnostic.IDE0052.severity = none
# ReSharper properties
resharper_autodetect_indent_settings = true
resharper_formatter_off_tag = @formatter:off
resharper_formatter_on_tag = @formatter:on
resharper_formatter_tags_enabled = true
resharper_new_line_before_while = true
resharper_place_attribute_on_same_line = false
resharper_show_autodetect_configure_formatting_tip = false
resharper_use_indent_from_vs = false
# CA2254: 模板应为静态表达式
dotnet_diagnostic.CA2254.severity = none
# ReSharper inspection severities
resharper_arrange_redundant_parentheses_highlighting = hint
resharper_arrange_this_qualifier_highlighting = hint
resharper_arrange_type_member_modifiers_highlighting = hint
resharper_arrange_type_modifiers_highlighting = hint
resharper_built_in_type_reference_style_for_member_access_highlighting = hint
resharper_built_in_type_reference_style_highlighting = hint
resharper_redundant_base_qualifier_highlighting = warning
resharper_suggest_var_or_type_built_in_types_highlighting = hint
resharper_suggest_var_or_type_elsewhere_highlighting = hint
resharper_suggest_var_or_type_simple_types_highlighting = hint
resharper_web_config_module_not_resolved_highlighting = warning
resharper_web_config_type_not_resolved_highlighting = warning
resharper_web_config_wrong_module_highlighting = warning
# CA1854: 首选 “IDictionary.TryGetValue(TKey, out TValue)” 方法
dotnet_diagnostic.CA1854.severity = none
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
# IDE0090: 使用 "new(...)"
dotnet_diagnostic.IDE0090.severity = none
[*.{cs,css,js,json,*html,razor,txt,log}]
charset = utf-8-bom
# IDE0305: 简化集合初始化
dotnet_diagnostic.IDE0305.severity = none
[*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}]
indent_size = 2
# Xml config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
[*.json]
indent_size = 2
[*.{ps1,psm1}]
indent_size = 4
[*.sh]
indent_size = 4
end_of_line = lf
###############################
# .NET Coding Conventions #
###############################
[*.{cs,vb}]
# Organize usings
dotnet_sort_system_directives_first = false
# this. preferences
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_readonly_field = true:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
###############################
# Naming Conventions #
###############################
# Style Definitions
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Use PascalCase for constant fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
end_of_line = crlf
dotnet_style_prefer_collection_expression = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
dotnet_code_quality_unused_parameters = all:suggestion
###############################
# C# Coding Conventions #
###############################
[*.cs]
# var preferences
csharp_style_var_for_built_in_types = true:silent
csharp_style_var_when_type_is_apparent = true:silent
csharp_style_var_elsewhere = true:silent
csharp_prefer_static_local_function = true:silent
# Expression-bodied members
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
# Null-checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
# Expression-level preferences
csharp_prefer_braces = true:silent
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
###############################
# C# Formatting Rules #
###############################
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# Space preferences
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
###############################
# VB Coding Conventions #
###############################
[*.vb]
# Modifier preferences
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
[*.cs]
# Add file header
file_header_template = Copyright (c) Argo Zhang (argo@163.com). All rights reserved.\nLicensed under the Apache License, Version 2.0. See License.txt in the project root for license information.\nWebsite: https://www.blazor.zone or https://argozhang.github.io/
csharp_style_namespace_declarations = file_scoped:suggestion
csharp_style_expression_bodied_local_functions = true:silent
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_prefer_readonly_struct_member = true:suggestion
csharp_style_prefer_readonly_struct = true:suggestion
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
csharp_style_prefer_switch_expression = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion

13
src/Delete .vs .bat Normal file
View File

@@ -0,0 +1,13 @@
@echo off
chcp 65001
setlocal enabledelayedexpansion
set "folder=%~dp0"
attrib -s -h "%folder%\.vs" >nul 2>&1
if exist "%folder%\.vs" (
rd /s /q "%folder%\.vs"
echo 删除了.vs文件夹%folder%\.vs
)
echo 删除完成!
pause

19
src/Delete bin Or obj.bat Normal file
View File

@@ -0,0 +1,19 @@
@echo off
chcp 65001
setlocal enabledelayedexpansion
set "folder=%~dp0"
for /r "%folder%" /d %%i in (*) do (
set "dirname=%%~nxi"
if /I "!dirname!"=="bin" (
rd /s /q "%%i"
echo 删除了名称为"bin"的文件夹:%%i
)
if /I "!dirname!"=="obj" (
rd /s /q "%%i"
echo 删除了名称为"obj"的文件夹:%%i
)
)
echo 删除完成!
pause

10
src/Delete nupkgs.bat Normal file
View File

@@ -0,0 +1,10 @@
@echo off
chcp 65001
setlocal enabledelayedexpansion
set "folder=%~dp0/nupkgs"
rd /s /q "%folder%"
echo 删除了名称为"nupkgs"的文件夹
echo 删除完成!
pause

View File

@@ -1,27 +1,26 @@
<Project>
<PropertyGroup>
<NoWarn>CS8618;CS8625;CS8600;CS8601;CS8604;CS8714;CS8602;CS8603;CS8619;CS8621</NoWarn>
<TargetFrameworks>net6.0;</TargetFrameworks>
<!--<TargetFrameworks>net6.0;</TargetFrameworks>-->
<LangVersion>11.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>5.0.0.8</Version>
<Authors>Diego</Authors>
<Company>Diego</Company>
<Product>Diego</Product>
<Copyright>版权所有 © 2023-present Diego</Copyright>
<RepositoryUrl>https://gitee.com/diego2098/ThingsGateway</RepositoryUrl>
<RepositoryType>Gitee</RepositoryType>
<GenerateResxSourceIncludeDefaultValues>true</GenerateResxSourceIncludeDefaultValues>
</PropertyGroup>
<PropertyGroup Condition="'$(SolutionName)'=='ThingsGateway - Admin'">
<DefineConstants>Admin</DefineConstants>
</PropertyGroup>
<ItemGroup>
<None Include="$(SolutionDir)..\README.md" Pack="true" PackagePath="\" />
<None Include="$(SolutionDir)..\LICENSE" Pack="true" PackagePath="\" />
<None Include="$(SolutionDir)..\icon.png" Pack="true" PackagePath="\" />
<None Include="$(SolutionDir)Directory.Build.props" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>
<PropertyGroup>
<NoWarn>CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;</NoWarn>
<TargetFrameworks>net6.0;</TargetFrameworks>
<LangVersion>12.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>6.0.0.9</Version>
<Authors>Diego</Authors>
<Company>Diego</Company>
<Product>Diego</Product>
<Copyright>版权所有 © 2023-present Diego</Copyright>
<RepositoryUrl>https://gitee.com/diego2098/ThingsGateway</RepositoryUrl>
<RepositoryType>Gitee</RepositoryType>
<GenerateResxSourceIncludeDefaultValues>true</GenerateResxSourceIncludeDefaultValues>
</PropertyGroup>
<ItemGroup>
<None Include="$(SolutionDir)..\README.md" Pack="true" PackagePath="\" />
<None Include="$(SolutionDir)..\LICENSE" Pack="true" PackagePath="\" />
<None Include="$(SolutionDir)..\icon.png" Pack="true" PackagePath="\" />
<None Include="$(SolutionDir)Directory.Build.props" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>

View File

@@ -1,21 +1,14 @@
<Project>
<PropertyGroup>
<TargetFrameworks>net45;netstandard2.0;net6.0;</TargetFrameworks>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageOutputPath>$(SolutionDir)nupkgs</PackageOutputPath>
<PackageVersion>$(Version)</PackageVersion>
<PackageTags>ThingsGateway;Diego;Blazor;设备采集;边缘网关;物联网</PackageTags>
<PackageProjectUrl>https://gitee.com/diego2098/ThingsGateway</PackageProjectUrl>
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<OutputPath></OutputPath>
</PropertyGroup>
<ItemGroup>
<None Include="$(SolutionDir)Foundation.props" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>
<PropertyGroup>
<TargetFrameworks>net462;netstandard2.0;net6.0;</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Content Remove="Locales\*.json" />
<EmbeddedResource Include="Locales\*.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="$(SolutionDir)Foundation.props" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>

View File

@@ -1,9 +1,11 @@
<Project>
<PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<!--<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>-->
<PackageOutputPath>$(SolutionDir)nupkgs</PackageOutputPath>
<PackageVersion>$(Version)</PackageVersion>
<PackageTags>ThingsGateway;Diego;Blazor;设备采集;边缘网关;物联网</PackageTags>
@@ -11,9 +13,11 @@
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<OutputPath></OutputPath>
</PropertyGroup>
<DebugSymbols>True</DebugSymbols>
<DebugType>Embedded</DebugType>
<EmbedAllSources>True</EmbedAllSources>
</PropertyGroup>
<ItemGroup>
<None Include="$(SolutionDir)PackNuget.props" Pack="true" PackagePath="\" />
</ItemGroup>

View File

@@ -1,17 +1,22 @@
<Project>
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0;net8.0;</TargetFrameworks>
<TargetFrameworks>net6.0;</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<None Include="$(SolutionDir)Plugin.props" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<Content Remove="Locales\*.json" />
<EmbeddedResource Include="Locales\*.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)ThingsGateway.Gateway.Blazor\ThingsGateway.Gateway.Blazor.csproj">
<ProjectReference Include="$(SolutionDir)ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj">
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
</ItemGroup>
</Project>
</Project>

View File

@@ -1,35 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34322.80
VisualStudioVersion = 17.9.34622.214
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3CBF3D90-92F4-4932-8E63-19BD344B19D6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Server", "ThingsGateway.Server\ThingsGateway.Server.csproj", "{BDC88C7C-D37B-44FB-9E84-2619F8B8B007}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Razor", "ThingsGateway.Razor\ThingsGateway.Razor.csproj", "{D3E8D660-AD12-4C46-871F-144D9C83ED2B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Core", "ThingsGateway.Core\ThingsGateway.Core.csproj", "{2904D604-40E9-459C-81E4-2B55FFB27706}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "others", "others", "{2C6B6E2F-76B9-47E1-B3BB-A76595275F2A}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Delete .vs .bat = Delete .vs .bat
Delete Bin And Obj.bat = Delete Bin And Obj.bat
Directory.Build.props = Directory.Build.props
Plugin.props = Plugin.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Cache", "ThingsGateway.Cache\ThingsGateway.Cache.csproj", "{2D61A1A2-60EB-4B77-BE5B-DA4D6AE0D68D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Razor", "ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj", "{81C3B3AF-9DA6-404B-A20A-31F2FC1CE832}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Components", "ThingsGateway.Components\ThingsGateway.Components.csproj", "{D86209BA-BB60-40B1-AD55-1E8E08CCADD8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Application", "ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj", "{5EE2DAEF-05DF-4590-B1FA-42917D859F73}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Core", "ThingsGateway.Core\ThingsGateway.Core.csproj", "{62AF3C40-180D-4C4F-AF35-C1FD2346DA0E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.NewLife.X", "ThingsGateway.NewLife.X\ThingsGateway.NewLife.X.csproj", "{1A04C297-BA57-45F1-8D74-D1DD8FCE83B9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Core", "ThingsGateway.Admin.Core\ThingsGateway.Admin.Core.csproj", "{E43C28BD-E208-424E-8A67-75BE712DC7B4}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "admin", "admin", "{1A13C5CC-69C9-42D9-8046-96DDECA0467A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Application", "ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj", "{C7B0B47B-4AF5-452A-8CB7-B709DC694FAA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.ApiController", "ThingsGateway.Admin.ApiController\ThingsGateway.Admin.ApiController.csproj", "{D8A1033A-FBE8-4F51-A7B6-282891D69AAF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Blazor", "ThingsGateway.Admin.Blazor\ThingsGateway.Admin.Blazor.csproj", "{E2F43E54-D2B6-4789-86BA-53395CA78A44}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Core", "ThingsGateway.Web.Core\ThingsGateway.Web.Core.csproj", "{6AE58F3E-E996-448F-994A-C054EA01EC96}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Entry", "ThingsGateway.Web.Entry\ThingsGateway.Web.Entry.csproj", "{2733F3EB-8854-43B7-9DF4-859EA34B35CB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "admin", "admin", "{F315F04B-CC19-4ACC-8544-ACFE27D1EF2B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{BF1ED6B2-7779-4FE6-8332-85C6713F42BB}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{3D3C6EB5-8E66-4B4F-A2F6-8C66673F3116}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -37,57 +33,42 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2D61A1A2-60EB-4B77-BE5B-DA4D6AE0D68D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D61A1A2-60EB-4B77-BE5B-DA4D6AE0D68D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D61A1A2-60EB-4B77-BE5B-DA4D6AE0D68D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D61A1A2-60EB-4B77-BE5B-DA4D6AE0D68D}.Release|Any CPU.Build.0 = Release|Any CPU
{D86209BA-BB60-40B1-AD55-1E8E08CCADD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D86209BA-BB60-40B1-AD55-1E8E08CCADD8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D86209BA-BB60-40B1-AD55-1E8E08CCADD8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D86209BA-BB60-40B1-AD55-1E8E08CCADD8}.Release|Any CPU.Build.0 = Release|Any CPU
{62AF3C40-180D-4C4F-AF35-C1FD2346DA0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62AF3C40-180D-4C4F-AF35-C1FD2346DA0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62AF3C40-180D-4C4F-AF35-C1FD2346DA0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62AF3C40-180D-4C4F-AF35-C1FD2346DA0E}.Release|Any CPU.Build.0 = Release|Any CPU
{E43C28BD-E208-424E-8A67-75BE712DC7B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E43C28BD-E208-424E-8A67-75BE712DC7B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E43C28BD-E208-424E-8A67-75BE712DC7B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E43C28BD-E208-424E-8A67-75BE712DC7B4}.Release|Any CPU.Build.0 = Release|Any CPU
{C7B0B47B-4AF5-452A-8CB7-B709DC694FAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C7B0B47B-4AF5-452A-8CB7-B709DC694FAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7B0B47B-4AF5-452A-8CB7-B709DC694FAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7B0B47B-4AF5-452A-8CB7-B709DC694FAA}.Release|Any CPU.Build.0 = Release|Any CPU
{D8A1033A-FBE8-4F51-A7B6-282891D69AAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8A1033A-FBE8-4F51-A7B6-282891D69AAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8A1033A-FBE8-4F51-A7B6-282891D69AAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8A1033A-FBE8-4F51-A7B6-282891D69AAF}.Release|Any CPU.Build.0 = Release|Any CPU
{E2F43E54-D2B6-4789-86BA-53395CA78A44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E2F43E54-D2B6-4789-86BA-53395CA78A44}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2F43E54-D2B6-4789-86BA-53395CA78A44}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E2F43E54-D2B6-4789-86BA-53395CA78A44}.Release|Any CPU.Build.0 = Release|Any CPU
{6AE58F3E-E996-448F-994A-C054EA01EC96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6AE58F3E-E996-448F-994A-C054EA01EC96}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6AE58F3E-E996-448F-994A-C054EA01EC96}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AE58F3E-E996-448F-994A-C054EA01EC96}.Release|Any CPU.Build.0 = Release|Any CPU
{2733F3EB-8854-43B7-9DF4-859EA34B35CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2733F3EB-8854-43B7-9DF4-859EA34B35CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2733F3EB-8854-43B7-9DF4-859EA34B35CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2733F3EB-8854-43B7-9DF4-859EA34B35CB}.Release|Any CPU.Build.0 = Release|Any CPU
{BDC88C7C-D37B-44FB-9E84-2619F8B8B007}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BDC88C7C-D37B-44FB-9E84-2619F8B8B007}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BDC88C7C-D37B-44FB-9E84-2619F8B8B007}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BDC88C7C-D37B-44FB-9E84-2619F8B8B007}.Release|Any CPU.Build.0 = Release|Any CPU
{D3E8D660-AD12-4C46-871F-144D9C83ED2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3E8D660-AD12-4C46-871F-144D9C83ED2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3E8D660-AD12-4C46-871F-144D9C83ED2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3E8D660-AD12-4C46-871F-144D9C83ED2B}.Release|Any CPU.Build.0 = Release|Any CPU
{2904D604-40E9-459C-81E4-2B55FFB27706}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2904D604-40E9-459C-81E4-2B55FFB27706}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2904D604-40E9-459C-81E4-2B55FFB27706}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2904D604-40E9-459C-81E4-2B55FFB27706}.Release|Any CPU.Build.0 = Release|Any CPU
{81C3B3AF-9DA6-404B-A20A-31F2FC1CE832}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81C3B3AF-9DA6-404B-A20A-31F2FC1CE832}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81C3B3AF-9DA6-404B-A20A-31F2FC1CE832}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81C3B3AF-9DA6-404B-A20A-31F2FC1CE832}.Release|Any CPU.Build.0 = Release|Any CPU
{5EE2DAEF-05DF-4590-B1FA-42917D859F73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5EE2DAEF-05DF-4590-B1FA-42917D859F73}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5EE2DAEF-05DF-4590-B1FA-42917D859F73}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5EE2DAEF-05DF-4590-B1FA-42917D859F73}.Release|Any CPU.Build.0 = Release|Any CPU
{1A04C297-BA57-45F1-8D74-D1DD8FCE83B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A04C297-BA57-45F1-8D74-D1DD8FCE83B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A04C297-BA57-45F1-8D74-D1DD8FCE83B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A04C297-BA57-45F1-8D74-D1DD8FCE83B9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{2D61A1A2-60EB-4B77-BE5B-DA4D6AE0D68D} = {BF1ED6B2-7779-4FE6-8332-85C6713F42BB}
{D86209BA-BB60-40B1-AD55-1E8E08CCADD8} = {BF1ED6B2-7779-4FE6-8332-85C6713F42BB}
{62AF3C40-180D-4C4F-AF35-C1FD2346DA0E} = {BF1ED6B2-7779-4FE6-8332-85C6713F42BB}
{E43C28BD-E208-424E-8A67-75BE712DC7B4} = {F315F04B-CC19-4ACC-8544-ACFE27D1EF2B}
{C7B0B47B-4AF5-452A-8CB7-B709DC694FAA} = {F315F04B-CC19-4ACC-8544-ACFE27D1EF2B}
{D8A1033A-FBE8-4F51-A7B6-282891D69AAF} = {F315F04B-CC19-4ACC-8544-ACFE27D1EF2B}
{E2F43E54-D2B6-4789-86BA-53395CA78A44} = {F315F04B-CC19-4ACC-8544-ACFE27D1EF2B}
{D3E8D660-AD12-4C46-871F-144D9C83ED2B} = {3D3C6EB5-8E66-4B4F-A2F6-8C66673F3116}
{2904D604-40E9-459C-81E4-2B55FFB27706} = {3D3C6EB5-8E66-4B4F-A2F6-8C66673F3116}
{81C3B3AF-9DA6-404B-A20A-31F2FC1CE832} = {1A13C5CC-69C9-42D9-8046-96DDECA0467A}
{5EE2DAEF-05DF-4590-B1FA-42917D859F73} = {1A13C5CC-69C9-42D9-8046-96DDECA0467A}
{1A04C297-BA57-45F1-8D74-D1DD8FCE83B9} = {3D3C6EB5-8E66-4B4F-A2F6-8C66673F3116}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
RESX_NeutralResourcesLanguage = zh-Hans
SolutionGuid = {789BA852-9F20-4421-A555-E665A12587D1}
SolutionGuid = {199B1B96-4F56-4828-9531-813BA02DB282}
EndGlobalSection
EndGlobal

View File

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

View File

@@ -1,79 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Microsoft.AspNetCore.Mvc;
namespace ThingsGateway.Admin.ApiController;
/// <summary>
/// 导出文件
/// </summary>
[ApiDescriptionSettings(CateGoryConst.ThingsGatewayAdmin, Order = 200)]
[Route("export")]
[LoggingMonitor]
public class ExportController : ControllerBase
{
private readonly IOperateLogService _operateLogService;
private readonly IVisitLogService _visitLogService;
/// <summary>
/// <inheritdoc cref="ExportController"/>
/// </summary>
public ExportController(
IOperateLogService operateLogService,
IVisitLogService visitLogService
)
{
_operateLogService = operateLogService;
_visitLogService = visitLogService;
}
/// <summary>
/// 下载操作日志
/// </summary>
/// <returns></returns>
[HttpGet("operateLog")]
public async Task<IActionResult> DownloadOperateLogAsync([FromQuery] OperateLogInput input)
{
if (input.All)
{
var fileStreamResult = await _operateLogService.ExportFileAsync();
return fileStreamResult;
}
else
{
var fileStreamResult = await _operateLogService.ExportFileAsync(input);
return fileStreamResult;
}
}
/// <summary>
/// 下载访问日志
/// </summary>
/// <returns></returns>
[HttpGet("visitLog")]
public async Task<IActionResult> DownloadVisitLogAsync([FromQuery] VisitLogInput input)
{
if (input.All)
{
var fileStreamResult = await _visitLogService.ExportFileAsync();
return fileStreamResult;
}
else
{
var fileStreamResult = await _visitLogService.ExportFileAsync(input);
return fileStreamResult;
}
}
}

View File

@@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<DocumentationFile>$(MSBuildProjectName).xml</DocumentationFile>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,54 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>ThingsGateway.Admin.ApiController</name>
</assembly>
<members>
<member name="T:ThingsGateway.Admin.ApiController.AuthController">
<summary>
后台登录控制器
</summary>
</member>
<member name="M:ThingsGateway.Admin.ApiController.AuthController.#ctor(ThingsGateway.Admin.Application.IAuthService)">
<summary>
<inheritdoc cref="T:ThingsGateway.Admin.ApiController.AuthController"/>
</summary>
<param name="authService"></param>
</member>
<member name="M:ThingsGateway.Admin.ApiController.AuthController.LoginAsync(ThingsGateway.Admin.Application.LoginInput)">
<summary>
后台登录
</summary>
<param name="input"></param>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.ApiController.AuthController.LogoutAsync(ThingsGateway.Admin.Application.LoginOutIput)">
<summary>
后台登出
</summary>
<returns></returns>
</member>
<member name="T:ThingsGateway.Admin.ApiController.ExportController">
<summary>
导出文件
</summary>
</member>
<member name="M:ThingsGateway.Admin.ApiController.ExportController.#ctor(ThingsGateway.Admin.Application.IOperateLogService,ThingsGateway.Admin.Application.IVisitLogService)">
<summary>
<inheritdoc cref="T:ThingsGateway.Admin.ApiController.ExportController"/>
</summary>
</member>
<member name="M:ThingsGateway.Admin.ApiController.ExportController.DownloadOperateLogAsync(ThingsGateway.Admin.Application.OperateLogInput)">
<summary>
下载操作日志
</summary>
<returns></returns>
</member>
<member name="M:ThingsGateway.Admin.ApiController.ExportController.DownloadVisitLogAsync(ThingsGateway.Admin.Application.VisitLogInput)">
<summary>
下载访问日志
</summary>
<returns></returns>
</member>
</members>
</doc>

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,39 +9,160 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Rougamo;
using Rougamo.Context;
using System.Collections.Concurrent;
using ThingsGateway.Core.Extension;
using ThingsGateway.Core.Json.Extension;
using UAParser;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 操作事件说明特性
/// Aop拦截器
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class OperDescAttribute : Attribute
public class OperDescAttribute : MoAttribute
{
/// <summary>
/// 操作记录标识
/// 日志消息队列(线程安全)
/// </summary>
/// <param name="description"></param>
/// <param name="catcategory"></param>
public OperDescAttribute(string description, string catcategory = CateGoryConst.Log_OPERATE)
private static readonly ConcurrentQueue<SysOperateLog> _logMessageQueue = new();
static OperDescAttribute()
{
Description = description;
Catcategory = catcategory;
// 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中
Task.Factory.StartNew(ProcessQueue, TaskCreationOptions.LongRunning);
}
/// <summary>
/// 分类
/// </summary>
public string Catcategory { get; }
public OperDescAttribute(string description, bool isRecordPar = true, object localizerType = null)
{
Description = description;
IsRecordPar = isRecordPar;
LocalizerType = (Type)localizerType;
}
public override AccessFlags Flags => AccessFlags.Public | AccessFlags.Method;
public override Feature Features => Feature.OnException | Feature.OnSuccess;
/// <summary>
/// 说明
/// 说明需配置本地化json文件
/// </summary>
public string Description { get; }
public Type? LocalizerType { get; }
/// <summary>
/// 记录参数默认true
/// 是否记录进出参数
/// </summary>
public bool IsRecordPar { get; set; } = true;
}
public bool IsRecordPar { get; }
public override void OnSuccess(MethodContext context)
{
//插入操作日志
SysOperateLog log = GetOperLog(LocalizerType, context);
WriteToQueue(log);
}
public override void OnException(MethodContext context)
{
//插入异常日志
SysOperateLog log = GetOperLog(LocalizerType, context);
log.Category = LogCateGoryEnum.Exception;//操作类型为异常
log.ExeStatus = false;//操作状态为失败
if (context.Exception is UserFriendlyException exception)
log.ExeMessage = exception?.Message;
else
log.ExeMessage = context.Exception?.ToString();
WriteToQueue(log);
}
/// <summary>
/// 将日志消息写入数据库中
/// </summary>
private static async Task ProcessQueue()
{
var db = DbContext.Db.GetConnectionScopeWithAttr<SysOperateLog>().CopyNew();
var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!;
while (!(appLifetime.ApplicationStopping.IsCancellationRequested || appLifetime.ApplicationStopped.IsCancellationRequested))
{
try
{
if (_logMessageQueue.Count > 0)
{
await db.InsertableWithAttr(_logMessageQueue.ToListWithDequeue()).ExecuteCommandAsync();//入库
}
await Task.Delay(3000, appLifetime.ApplicationStopping).ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
private SysOperateLog GetOperLog(Type? localizerType, MethodContext context)
{
var str = App.HttpContext?.Request?.Headers?.UserAgent;
var methodBase = context.Method;
ClientInfo? clientInfo = null;
if (str.HasValue)
{
clientInfo = Parser.GetDefault().Parse(str);
}
string? paramJson = null;
if (IsRecordPar)
{
var args = context.Arguments;
var parametersInfo = methodBase.GetParameters();
var parametersDict = new Dictionary<string, object>();
for (int i = 0; i < parametersInfo.Length; i++)
{
parametersDict[parametersInfo[i].Name!] = args[i];
}
paramJson = parametersDict.ToSystemTextJsonString();
}
var result = context.ReturnValue;
var resultJson = IsRecordPar ? result?.ToSystemTextJsonString() : null;
//操作日志表实体
var log = new SysOperateLog
{
Name = (localizerType == null ? App.CreateLocalizerByType(typeof(OperDescAttribute)) : App.CreateLocalizerByType(localizerType))![Description],
Category = LogCateGoryEnum.Operate,
ExeStatus = true,
OpIp = App.HttpContext?.Connection?.RemoteIpAddress?.MapToIPv4()?.ToString(),
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
OpTime = DateTime.Now,
OpAccount = UserManager.UserAccount,
ReqUrl = null,
ReqMethod = "browser",
ResultJson = resultJson,
ClassName = methodBase.ReflectedType!.Name,
MethodName = methodBase.Name,
ParamJson = paramJson,
VerificatId = UserManager.VerificatId,
};
return log;
}
/// <summary>
/// 将日志消息写入队列中等待后台任务出队写入数据库
/// </summary>
/// <param name="logMsg">结构化日志消息</param>
private void WriteToQueue(SysOperateLog logMsg)
{
_logMessageQueue.Enqueue(logMsg);
}
}

View File

@@ -1,262 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.Reflection;
using Furion.Reflection.Extensions;
using Microsoft.Extensions.Hosting;
using System.Collections.Concurrent;
using System.Reflection;
using System.Text;
using ThingsGateway.Core.Extension.ConcurrentQueue;
using ThingsGateway.Core.Extension.Json;
using UAParser;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// AOP处理操作日志
/// </summary>
public class OperDispatchProxy : AspectDispatchProxy, IDispatchProxy
{
/// <summary>
/// 服务提供器可以用来解析服务Services.GetService()
/// </summary>
public IServiceProvider Services { get; set; }
/// <summary>
/// 当前服务实例
/// </summary>
public object Target { get; set; }
/// <summary>
/// 方法
/// </summary>
/// <param name="method"></param>
/// <param name="args"></param>
/// <returns></returns>
public override object Invoke(MethodInfo method, object[] args)
{
var desc = method.GetActualCustomAttribute<OperDescAttribute>(Target);
if (desc == null)
{
return Invoke(method, args);
}
else
{
Exception exception = default;
object result = default;
try
{
result = Invoke(method, args);
}
catch (Exception ex)
{
exception = ex;
}
WriteOperLog(method, args, desc, result, exception);
if (exception != null)
{
throw exception;
}
return result;//返回结果
}
object Invoke(MethodInfo method, object[] args)
{
//如果不带返回值
if (method.ReturnType == typeof(void))
{
return method.Invoke(Target, args);//直接返回
}
else
{
var result = method.Invoke(Target, args);
return result;//返回结果
}
}
}
/// <summary>
/// 异步无返回值
/// </summary>
/// <param name="method"></param>
/// <param name="args"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override async Task InvokeAsync(MethodInfo method, object[] args)
{
var desc = method.GetActualCustomAttribute<OperDescAttribute>(Target, true);
if (desc == null)
{
var task = method.Invoke(Target, args) as Task;
await task;
}
else
{
Exception exception = default;
try
{
var task = method.Invoke(Target, args) as Task;
await task;
}
catch (Exception ex)
{
exception = ex;
}
WriteOperLog(method, args, desc, null, exception);
if (exception != null)
{
throw exception;
}
}
}
/// <summary>
/// 异步带返回值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="method"></param>
/// <param name="args"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override async Task<T> InvokeAsyncT<T>(MethodInfo method, object[] args)
{
var desc = method.GetActualCustomAttribute<OperDescAttribute>(Target, true);
if (desc == null)
{
var taskT = method.Invoke(Target, args) as Task<T>;
var result = await taskT;
return result;//返回结果
}
else
{
T result = default;
//写入操作日志
Exception exception = null;
try
{
var taskT = method.Invoke(Target, args) as Task<T>;
result = await taskT;
}
catch (Exception ex)
{
exception = ex;
}
WriteOperLog(method, args, desc, result, exception);
if (exception != null)
{
throw exception;
}
else
{
return result;//返回结果
}
}
}
private void WriteOperLog(MethodInfo method, object[] args, OperDescAttribute desc, object result, Exception exception)
{
//写入操作日志
var str = App.HttpContext?.Request?.Headers?.UserAgent;
ClientInfo clientInfo = null;
if (str.HasValue)
{
clientInfo = Parser.GetDefault().Parse(str);
}
StringBuilder stringBuilder = new();
if (desc.IsRecordPar)
{
var parameters = method.GetParameters();
var jsonParameters = parameters.Select((p, i) => $"\"{p.Name}\": {args[i].ToJsonString()}");
stringBuilder.Append('{');
stringBuilder.Append(string.Join(", ", jsonParameters));
stringBuilder.Append('}');
}
var paramJson = stringBuilder.ToString();
var resultJson = desc.IsRecordPar ? result?.ToJsonString() : null;
//操作日志表实体
var log = new SysOperateLog
{
Name = desc.Description,
Category = desc.Catcategory,
ExeStatus = LogConst.SUCCESS,
OpIp = App.HttpContext?.Connection?.RemoteIpAddress?.MapToIPv4().ToString(),
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
OpTime = DateTime.Now,
OpAccount = UserManager.UserAccount,
ReqUrl = "",
ReqMethod = LogConst.LOG_REQMETHOD,
ResultJson = resultJson,
ClassName = method.ReflectedType.Name,
MethodName = method.Name,
ParamJson = paramJson,
VerificatId = UserManager.VerificatId,
};
//如果异常不为空
if (exception != null)
{
log.Category = CateGoryConst.Log_EXCEPTION;//操作类型为异常
log.ExeStatus = LogConst.FAIL;//操作状态为失败
log.ExeMessage = exception.Source + ":" + exception.Message + Environment.NewLine + exception.StackTrace;
}
WriteToQueue(log);
}
/// <summary>
/// 日志消息队列(线程安全)
/// </summary>
private static readonly ConcurrentQueue<SysOperateLog> _logMessageQueue = new();
static OperDispatchProxy()
{
// 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中
Task.Factory.StartNew(ProcessQueue, TaskCreationOptions.LongRunning);
}
/// <summary>
/// 将日志消息写入队列中等待后台任务出队写入数据库
/// </summary>
/// <param name="logMsg">结构化日志消息</param>
private void WriteToQueue(SysOperateLog logMsg)
{
_logMessageQueue.Enqueue(logMsg);
}
/// <summary>
/// 将日志消息写入数据库中
/// </summary>
private static async Task ProcessQueue()
{
var db = DbContext.Db.CopyNew();
var appLifetime = App.GetService<IHostApplicationLifetime>();
while (!(appLifetime.ApplicationStopping.IsCancellationRequested || appLifetime.ApplicationStopped.IsCancellationRequested))
{
if (_logMessageQueue.Count > 0)
{
await db.InsertableWithAttr(_logMessageQueue.ToListWithDequeue()).ExecuteCommandAsync();//入库
}
await Task.Delay(3000);
}
}
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,9 +9,10 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 忽略Excel导入导出

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,9 +9,10 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 需要角色授权权限

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,13 +9,15 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 种子数据忽略新增
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class IgnoreSeedDataAddAttribute : Attribute
{
}
@@ -24,6 +25,7 @@ public class IgnoreSeedDataAddAttribute : Attribute
/// <summary>
/// 种子数据忽略修改
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class IgnoreSeedDataUpdateAttribute : Attribute
{
}
@@ -31,6 +33,7 @@ public class IgnoreSeedDataUpdateAttribute : Attribute
/// <summary>
/// 忽略初始化表
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class IgnoreInitTableAttribute : Attribute
{
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,9 +9,10 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 管理员才能访问

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,11 +9,12 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 最小值校验
@@ -25,18 +25,12 @@ public class MinValueAttribute : ValidationAttribute
/// 最小值
/// </summary>
/// <param name="value"></param>
public MinValueAttribute(double value)
public MinValueAttribute(UInt64 value)
{
MinValue = value;
}
private double MinValue { get; set; }
/// <inheritdoc/>
public override string FormatErrorMessage(string name)
{
return base.FormatErrorMessage(name);
}
private UInt64 MinValue { get; set; }
/// <summary>
/// 最小值校验
@@ -47,10 +41,10 @@ public class MinValueAttribute : ValidationAttribute
{
if (value is null)
{
return true;
return false;
}
var input = Convert.ToDouble(value);
return input > MinValue;
var input = Convert.ToUInt64(value);
return input >= MinValue;
}
}

View File

@@ -0,0 +1,676 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections;
namespace ThingsGateway.Admin.Application.ConcurrentList;
/// <summary>
/// 线程安全的List其基本操作和List一致。
/// </summary>
/// <typeparam name="T"></typeparam>
public class ConcurrentList<T> : IList<T>, IReadOnlyList<T>
{
private readonly List<T> m_list;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="collection"></param>
public ConcurrentList(IEnumerable<T> collection)
{
this.m_list = new List<T>(collection);
}
/// <summary>
/// 构造函数
/// </summary>
public ConcurrentList()
{
this.m_list = new List<T>();
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="capacity"></param>
public ConcurrentList(int capacity)
{
this.m_list = new List<T>(capacity);
}
/// <summary>
/// 元素数量
/// </summary>
public int Count
{
get
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.Count;
}
}
}
/// <summary>
/// 是否为只读
/// </summary>
public bool IsReadOnly => false;
/// <summary>
/// 获取索引元素
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T this[int index]
{
get
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list[index];
}
}
set
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list[index] = value;
}
}
}
/// <summary>
/// 添加元素
/// </summary>
/// <param name="item"></param>
public void Add(T item)
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.Add(item);
}
}
/// <summary>
/// 清空所有元素
/// </summary>
public void Clear()
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.Clear();
}
}
/// <summary>
/// 是否包含某个元素
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool Contains(T item)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.Contains(item);
}
}
/// <summary>
/// 复制到
/// </summary>
/// <param name="array"></param>
/// <param name="arrayIndex"></param>
public void CopyTo(T[] array, int arrayIndex)
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.CopyTo(array, arrayIndex);
}
}
/// <summary>
/// 返回迭代器
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.ToList().GetEnumerator();
}
}
/// <summary>
/// 返回迭代器组合
/// </summary>
/// <returns></returns>
IEnumerator IEnumerable.GetEnumerator()
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.GetEnumerator();
}
}
/// <summary>
/// 索引
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public int IndexOf(T item)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.IndexOf(item);
}
}
/// <summary>
/// 插入
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
public void Insert(int index, T item)
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.Insert(index, item);
}
}
/// <summary>
/// 移除元素
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool Remove(T item)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.Remove(item);
}
}
/// <summary>
/// 按索引移除
/// </summary>
/// <param name="index"></param>
public void RemoveAt(int index)
{
lock (((ICollection)this.m_list).SyncRoot)
{
if (index < this.m_list.Count)
{
this.m_list.RemoveAt(index);
}
}
}
/// <summary>
/// 获取或设置容量
/// </summary>
public int Capacity
{
get
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.Capacity;
}
}
set
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.Capacity = value;
}
}
}
/// <summary>
/// <inheritdoc cref="List{T}.AddRange(IEnumerable{T})"/>
/// </summary>
/// <param name="collection"></param>
public void AddRange(IEnumerable<T> collection)
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.AddRange(collection);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.BinarySearch(T)"/>
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public int BinarySearch(T item)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.BinarySearch(item);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.BinarySearch(T, IComparer{T})"/>
/// </summary>
/// <param name="item"></param>
/// <param name="comparer"></param>
/// <returns></returns>
public int BinarySearch(T item, IComparer<T> comparer)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.BinarySearch(item, comparer);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.BinarySearch(int, int, T, IComparer{T})"/>
/// </summary>
/// <param name="index"></param>
/// <param name="count"></param>
/// <param name="item"></param>
/// <param name="comparer"></param>
/// <returns></returns>
public int BinarySearch(int index, int count, T item, IComparer<T> comparer)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.BinarySearch(index, count, item, comparer);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.ConvertAll{TOutput}(Converter{T, TOutput})"/>
/// </summary>
/// <typeparam name="TOutput"></typeparam>
/// <param name="converter"></param>
/// <returns></returns>
public List<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.ConvertAll(converter);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.Find(Predicate{T})"/>
/// </summary>
/// <param name="match"></param>
/// <returns></returns>
public T Find(Predicate<T> match)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.Find(match);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.FindAll(Predicate{T})"/>
/// </summary>
/// <param name="match"></param>
/// <returns></returns>
public List<T> FindAll(Predicate<T> match)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.FindAll(match);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.FindIndex(int, int, Predicate{T})"/>
/// </summary>
/// <param name="startIndex"></param>
/// <param name="count"></param>
/// <param name="match"></param>
/// <returns></returns>
public int FindIndex(int startIndex, int count, Predicate<T> match)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.FindIndex(startIndex, count, match);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.FindIndex(int, Predicate{T})"/>
/// </summary>
/// <param name="startIndex"></param>
/// <param name="match"></param>
/// <returns></returns>
public int FindIndex(int startIndex, Predicate<T> match)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.FindIndex(startIndex, match);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.FindIndex(Predicate{T})"/>
/// </summary>
/// <param name="match"></param>
/// <returns></returns>
public int FindIndex(Predicate<T> match)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.FindIndex(match);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.FindLast(Predicate{T})"/>
/// </summary>
/// <param name="match"></param>
/// <returns></returns>
public T FindLast(Predicate<T> match)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.FindLast(match);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.FindLastIndex(int, int, Predicate{T})"/>
/// </summary>
/// <param name="startIndex"></param>
/// <param name="count"></param>
/// <param name="match"></param>
/// <returns></returns>
public int FindLastIndex(int startIndex, int count, Predicate<T> match)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.FindLastIndex(startIndex, count, match);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.FindLastIndex(int, Predicate{T})"/>
/// </summary>
/// <param name="startIndex"></param>
/// <param name="match"></param>
/// <returns></returns>
public int FindLastIndex(int startIndex, Predicate<T> match)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.FindLastIndex(startIndex, match);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.FindLastIndex(Predicate{T})"/>
/// </summary>
/// <param name="match"></param>
/// <returns></returns>
public int FindLastIndex(Predicate<T> match)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.FindLastIndex(match);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.ForEach(Action{T})"/>
/// </summary>
/// <param name="action"></param>
public void ForEach(Action<T> action)
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.ForEach(action);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.GetRange(int, int)"/>
/// </summary>
/// <param name="index"></param>
/// <param name="count"></param>
/// <returns></returns>
public List<T> GetRange(int index, int count)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.GetRange(index, count);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.IndexOf(T, int)"/>
/// </summary>
/// <param name="item"></param>
/// <param name="index"></param>
/// <returns></returns>
public int IndexOf(T item, int index)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.IndexOf(item, index);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.IndexOf(T, int, int)"/>
/// </summary>
/// <param name="item"></param>
/// <param name="index"></param>
/// <param name="count"></param>
/// <returns></returns>
public int IndexOf(T item, int index, int count)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.IndexOf(item, index, count);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.InsertRange(int, IEnumerable{T})"/>
/// </summary>
/// <param name="index"></param>
/// <param name="collection"></param>
public void InsertRange(int index, IEnumerable<T> collection)
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.InsertRange(index, collection);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.LastIndexOf(T)"/>
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public int LastIndexOf(T item)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.IndexOf(item);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.LastIndexOf(T, int)"/>
/// </summary>
/// <param name="item"></param>
/// <param name="index"></param>
/// <returns></returns>
public int LastIndexOf(T item, int index)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.LastIndexOf(item, index);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.LastIndexOf(T, int, int)"/>
/// </summary>
/// <param name="item"></param>
/// <param name="index"></param>
/// <param name="count"></param>
/// <returns></returns>
public int LastIndexOf(T item, int index, int count)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.LastIndexOf(item, index, count);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.RemoveAll(Predicate{T})"/>
/// </summary>
/// <param name="match"></param>
public void RemoveAll(Predicate<T> match)
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.RemoveAll(match);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.RemoveRange(int, int)"/>
/// </summary>
/// <param name="index"></param>
/// <param name="count"></param>
public void RemoveRange(int index, int count)
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.RemoveRange(index, count);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.Reverse()"/>
/// </summary>
public void Reverse()
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.Reverse();
}
}
/// <summary>
/// <inheritdoc cref="List{T}.Reverse(int, int)"/>
/// </summary>
/// <param name="index"></param>
/// <param name="count"></param>
public void Reverse(int index, int count)
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.Reverse(index, count);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.Sort()"/>
/// </summary>
public void Sort()
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.Sort();
}
}
/// <summary>
/// <inheritdoc cref="List{T}.Sort(Comparison{T})"/>
/// </summary>
/// <param name="comparison"></param>
public void Sort(Comparison<T> comparison)
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.Sort(comparison);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.Sort(IComparer{T})"/>
/// </summary>
/// <param name="comparer"></param>
public void Sort(IComparer<T> comparer)
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.Sort(comparer);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.Sort(int, int, IComparer{T})"/>
/// </summary>
/// <param name="index"></param>
/// <param name="count"></param>
/// <param name="comparer"></param>
public void Sort(int index, int count, IComparer<T> comparer)
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.Sort(index, count, comparer);
}
}
/// <summary>
/// <inheritdoc cref="List{T}.ToArray"/>
/// </summary>
/// <returns></returns>
public T[] ToArray()
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.ToArray();
}
}
/// <summary>
/// <inheritdoc cref="List{T}.TrimExcess"/>
/// </summary>
public void TrimExcess()
{
lock (((ICollection)this.m_list).SyncRoot)
{
this.m_list.TrimExcess();
}
}
/// <summary>
/// <inheritdoc cref="List{T}.TrueForAll(Predicate{T})"/>
/// </summary>
/// <param name="match"></param>
/// <returns></returns>
public bool TrueForAll(Predicate<T> match)
{
lock (((ICollection)this.m_list).SyncRoot)
{
return this.m_list.TrueForAll(match);
}
}
}

View File

@@ -1,6 +0,0 @@
{
//日志配置
"LogJob": {
"DaysAgo": 10 //清理日志
}
}

View File

@@ -1,6 +0,0 @@
{
//日志配置
"LogJob": {
"DaysAgo": 10 //清理日志
}
}

View File

@@ -0,0 +1,70 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application
{
public class CacheConst
{
public const string Cache_Prefix_Admin = "ThingsGatewayAdmin:";
/// <summary>
/// 系统字典表缓存Key
/// </summary>
public const string Cache_SysDict = $"{CacheConst.Cache_Prefix_Admin}SysDict:";
/// <summary>
/// 用户表缓存Key
/// </summary>
public const string Cache_SysUser = $"{CacheConst.Cache_Prefix_Admin}SysUser:";
/// <summary>
/// 用户账号关系缓存Key
/// </summary>
public const string Cache_SysUserAccount = $"{CacheConst.Cache_Prefix_Admin}SysUserAccount:";
/// <summary>
/// 资源表缓存Key
/// </summary>
public const string Cache_SysResource = $"{CacheConst.Cache_Prefix_Admin}SysResource:";
/// <summary>
/// 关系表缓存Key
/// </summary>
public const string Cache_SysRelation = $"{CacheConst.Cache_Prefix_Admin}SysRelation:";
/// <summary>
/// 角色表缓存Key
/// </summary>
public const string Cache_SysRole = $"{CacheConst.Cache_Prefix_Admin}SysRole:";
/// <summary>
/// Token表缓存Key
/// </summary>
public const string Cache_Token = $"{CacheConst.Cache_Prefix_Admin}Token:";
/// <summary>
/// Token表缓存Key
/// </summary>
public const string Cache_HardwareInfo = $"{CacheConst.Cache_Prefix_Admin}Cache_HardwareInfo:";
#region
/// <summary>
/// 登录错误次数缓存Key
/// </summary>
public const string Cache_LoginErrorCount = $"{CacheConst.Cache_Prefix_Admin}LoginErrorCount:";
#endregion
}
}

View File

@@ -1,158 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 分类常量
/// </summary>
public class CateGoryConst
{
#region
/// <summary>
/// 系统基础
/// </summary>
public const string Config_SYS_BASE = "SYS_BASE";
/// <summary>
/// 业务定义
/// </summary>
public const string Config_BIZ_DEFINE = "BIZ_DEFINE";
/// <summary>
/// 登录策略
/// </summary>
public const string LOGIN_POLICY = "LOGIN_POLICY";
/// <summary>
/// 密码策略
/// </summary>
public const string Config_PWD_POLICY = "PWD_POLICY";
#endregion
#region
/// <summary>
/// 菜单
/// </summary>
public const string Resource_MENU = "MENU";
/// <summary>
/// 单页
/// </summary>
public const string Resource_SPA = "SPA";
/// <summary>
/// 按钮
/// </summary>
public const string Resource_BUTTON = "BUTTON";
#endregion
#region
/// <summary>
/// 用户有哪些角色
/// </summary>
public const string Relation_SYS_USER_HAS_ROLE = "SYS_USER_HAS_ROLE";
/// <summary>
/// 角色有哪些资源
/// </summary>
public const string Relation_SYS_ROLE_HAS_RESOURCE = "SYS_ROLE_HAS_RESOURCE";
/// <summary>
///用户有哪些资源
/// </summary>
public const string Relation_SYS_USER_HAS_RESOURCE = "SYS_USER_HAS_RESOURCE";
/// <summary>
/// 角色有哪些权限
/// </summary>
public const string Relation_SYS_ROLE_HAS_PERMISSION = "SYS_ROLE_HAS_PERMISSION";
/// <summary>
/// 角色有哪些OPENAPI权限
/// </summary>
public const string Relation_SYS_ROLE_HAS_OPENAPIPERMISSION = "SYS_ROLE_HAS_OPENAPIPERMISSION";
/// <summary>
/// 用户有哪些权限
/// </summary>
public const string Relation_SYS_USER_HAS_PERMISSION = "SYS_USER_HAS_PERMISSION";
/// <summary>
/// 用户有哪些OPENAPI权限
/// </summary>
public const string Relation_SYS_USER_HAS_OPENAPIPERMISSION = "Relation_SYS_USER_HAS_OPENAPIPERMISSION";
/// <summary>
/// 用户工作台数据
/// </summary>
public const string Relation_SYS_USER_WORKBENCH_DATA = "SYS_USER_WORKBENCH_DATA";
/// <summary>
/// 用户主页数据
/// </summary>
public const string Relation_SYS_USER_DEFAULT_RAZOR = "Relation_SYS_USER_DEFAULT_RAZOR";
#endregion
#region
/// <summary>
/// 登录
/// </summary>
public const string Log_LOGIN = "LOGIN";
/// <summary>
/// 登出
/// </summary>
public const string Log_LOGOUT = "LOGOUT";
/// <summary>
/// 操作
/// </summary>
public const string Log_OPERATE = "OPERATE";
/// <summary>
/// 异常
/// </summary>
public const string Log_EXCEPTION = "EXCEPTION";
#endregion
#region
/// <summary>
/// 全局
/// </summary>
public const string Role_GLOBAL = "GLOBAL";
/// <summary>
/// Api
/// </summary>
public const string Role_API = "API";
#endregion
#region Api分组
public const string ThingsGatewayAdmin = "ThingsGateway.Admin";
public const string ThingsGatewayApi = "ThingsGateway.OpenApi";
#endregion Api分组
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,9 +9,10 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 授权用户常量
@@ -25,7 +25,7 @@ public class ClaimConst
public const string Account = "Account";
/// <summary>
/// 账号类型
/// SuperAdmin
/// </summary>
public const string SuperAdmin = "SuperAdmin";

View File

@@ -1,99 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 系统配置常量
/// </summary>
public class ConfigConst
{
/// <summary>
/// 系统默认工作台
/// </summary>
public const string SYS_DEFAULT_WORKBENCH_DATA = "SYS_DEFAULT_WORKBENCH_DATA";
/// <summary>
/// 系统默认主页
/// </summary>
public const string SYS_DEFAULT_DEFAULT_RAZOR = "SYS_DEFAULT_DEFAULT_RAZOR";
#region
/// <summary>
/// 登录验证码开关
/// </summary>
public const string LOGIN_CAPTCHA_OPEN = "LOGIN_CAPTCHA_OPEN";
/// <summary>
/// 单用户登录开关
/// </summary>
public const string LOGIN_SINGLE_OPEN = "LOGIN_SINGLE_OPEN";
/// <summary>
/// 登录错误锁定时长
/// </summary>
public const string LOGIN_ERROR_LOCK = "LOGIN_ERROR_LOCK";
/// <summary>
/// 登录错误锁定时长
/// </summary>
public const string LOGIN_ERROR_RESET_TIME = "LOGIN_ERROR_RESET_TIME";
/// <summary>
/// 登录错误次数
/// </summary>
public const string LOGIN_ERROR_COUNT = "LOGIN_ERROR_COUNT";
/// <summary>
/// Verificat过期时间(分)
/// </summary>
public const string LOGIN_VERIFICAT_EXPIRES = "LOGIN_VERIFICAT_EXPIRES";
#endregion
#region
/// <summary>
/// 默认用户密码
/// </summary>
public const string PWD_DEFAULT_PASSWORD = "PWD_DEFAULT_PASSWORD";
/// <summary>
/// 密码最小长度
/// </summary>
public const string PWD_MIN_LENGTH = "PWD_MIN_LENGTH";
/// <summary>
/// 包含数字
/// </summary>
public const string PWD_CONTAIN_NUM = "PWD_CONTAIN_NUM";
/// <summary>
/// 包含小写字母
/// </summary>
public const string PWD_CONTAIN_LOWER = "PWD_CONTAIN_LOWER";
/// <summary>
/// 包含大写字母
/// </summary>
public const string PWD_CONTAIN_UPPER = "PWD_CONTAIN_UPPER";
/// <summary>
/// 包含特殊字符
/// </summary>
public const string PWD_CONTAIN_CHARACTER = "PWD_CONTAIN_CHARACTER";
#endregion
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,7 +9,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
@@ -20,7 +20,22 @@ namespace ThingsGateway.Admin.Application;
public class ResourceConst
{
/// <summary>
/// 系统内置单页面编码
/// 系统内置编码
/// </summary>
public const string System = "system";
public const string System = "System";
/// <summary>
/// 系统管理内置ID 1
/// </summary>
public const long SystemId = 2;
/// <summary>
/// SPA内置ID 2
/// </summary>
public const long SpaId = 1;
/// <summary>
/// SPA内置
/// </summary>
public const string SpaTitle = "SPA";
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,7 +9,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
@@ -19,13 +19,23 @@ namespace ThingsGateway.Admin.Application;
/// </summary>
public class RoleConst
{
/// <summary>
/// 超级管理员Id
/// </summary>
public const long SuperAdminId = 212725263002001;
/// <summary>
/// 超级管理员
/// </summary>
public const string SuperAdmin = "superAdmin";
public const string SuperAdmin = "SuperAdmin";
/// <summary>
/// 业务管理员
/// </summary>
public const string BizAdmin = "bizAdmin";
}
public const string BizAdmin = "BizAdmin";
/// <summary>
/// api角色
/// </summary>
public const string ApiRole = "ApiRole";
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,9 +9,10 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// SqlSugar系统常量
@@ -29,6 +29,16 @@ public class SqlSugarConst
/// </summary>
public const string DB_Log = "DB_Log";
/// <summary>
/// DB_TokenCache
/// </summary>
public const string DB_TokenCache = "DB_TokenCache";
/// <summary>
/// DB_HardwareInfo
/// </summary>
public const string DB_HardwareInfo = "DB_HardwareInfo";
/// <summary>
/// DB_Custom
/// </summary>

View File

@@ -1,75 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 系统层常量
/// </summary>
public class SystemConst
{
/// <summary>
/// 系统配置表缓存Key
/// </summary>
public const string Cache_DevConfig = CacheConst.Cache_Prefix_Admin + "SysConfig:";
/// <summary>
/// 登录验证码缓存Key
/// </summary>
public const string Cache_Captcha = CacheConst.Cache_Prefix_Admin + "Captcha:";
/// <summary>
/// 用户表缓存Key
/// </summary>
public const string Cache_SysUser = CacheConst.Cache_Prefix_Admin + "SysUser";
/// <summary>
/// 用户手机号关系缓存Key
/// </summary>
public const string Cache_SysUserPhone = CacheConst.Cache_Prefix_Admin + "SysUserPhone";
/// <summary>
/// 用户手机号关系缓存Key
/// </summary>
public const string Cache_SysUserAccount = CacheConst.Cache_Prefix_Admin + "SysUserAccount";
/// <summary>
/// 资源表缓存Key
/// </summary>
public const string Cache_SysResource = CacheConst.Cache_Prefix_Admin + "SysResource:";
/// <summary>
/// 字典表缓存Key
/// </summary>
public const string Cache_DevDict = CacheConst.Cache_Prefix_Admin + "DevDict";
/// <summary>
/// 关系表缓存Key
/// </summary>
public const string Cache_SysRelation = CacheConst.Cache_Prefix_Admin + "SysRelation:";
/// <summary>
/// 角色表缓存Key
/// </summary>
public const string Cache_SysRole = CacheConst.Cache_Prefix_Admin + "SysRole";
#region
/// <summary>
/// 登录错误次数缓存Key
/// </summary>
public const string Cache_LoginErrorCount = CacheConst.Cache_Prefix_Admin + "LoginErrorCount:";
#endregion
}

View File

@@ -0,0 +1,51 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
namespace ThingsGateway.Admin.Application;
[Route("api/auth")]
[LoggingMonitor]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;
public AuthController(IAuthService authService)
{
_authService = authService;
}
[HttpPost("login")]
[AllowAnonymous]
public Task<LoginOutput> LoginAsync([FromBody] LoginInput input)
{
return _authService.LoginAsync(input);
}
[HttpGet("logout")]
[Authorize]
[IgnoreRolePermission]
public async Task<IActionResult> LogoutAsync([FromQuery] string returnUrl)
{
await _authService.LoginOutAsync();
return Redirect(QueryHelpers.AddQueryString(Request.PathBase + CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
{
["ReturnUrl"] = returnUrl
}));
}
}

View File

@@ -0,0 +1,75 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using RouteAttribute = Microsoft.AspNetCore.Mvc.RouteAttribute;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 文化 Controller
/// </summary>
[Route("[controller]/[action]")]
public class CultureController : Controller
{
/// <summary>
/// 设置文化方法
/// </summary>
/// <param name="culture"></param>
/// <param name="redirectUri"></param>
/// <returns></returns>
[HttpGet]
public IActionResult SetCulture(string culture, string redirectUri)
{
if (string.IsNullOrEmpty(culture))
{
HttpContext.Response.Cookies.Delete(CookieRequestCultureProvider.DefaultCookieName);
}
else
{
HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture, culture)), new CookieOptions()
{
Expires = DateTimeOffset.Now.AddYears(1)
});
//更改全局文化,采集后台也会变化
//var cultureInfo = new CultureInfo(culture);
//CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
//CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
//CultureInfo.CurrentCulture = cultureInfo;
//CultureInfo.CurrentUICulture = cultureInfo;
}
return LocalRedirect(redirectUri);
}
/// <summary>
/// 重置文化方法
/// </summary>
/// <param name="redirectUri"></param>
/// <returns></returns>
[HttpGet]
public IActionResult ResetCulture(string redirectUri)
{
HttpContext.Response.Cookies.Delete(CookieRequestCultureProvider.DefaultCookieName);
return LocalRedirect(redirectUri);
}
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,24 +9,18 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace ThingsGateway.Demo.Web
namespace ThingsGateway.Admin.Application
{
/// <summary>
/// 文件下载
/// </summary>
#if DEMO
#else
[ApiDescriptionSettings(IgnoreApi = true)]
#endif
[ApiController]
[Route("[controller]")]
[Route("api/file")]
public class FileController : ControllerBase
{
/// <summary>
@@ -35,7 +28,7 @@ namespace ThingsGateway.Demo.Web
/// </summary>
/// <param name="fileName">相对路径</param>
/// <returns></returns>
[HttpGet]
[HttpGet("download")]
public IActionResult Download(string fileName)
{
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", fileName);

View File

@@ -0,0 +1,350 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection;
using System.Collections;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Text.Encodings.Web;
using System.Text.Json;
using ThingsGateway.Core.Json.Extension;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 规范化RESTful风格返回值
/// </summary>
public class ResultFilter : IAsyncActionFilter
{
public const string ValidationFailedKey = $"{nameof(ResultFilter)}Validate";
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// 排除 WebSocket 请求处理
if (context.HttpContext.IsWebSocketRequest())
{
await next();
return;
}
var httpContext = context.HttpContext;
var unifyResult = httpContext.RequestServices.GetRequiredService<IUnifyResultProvider>();
#region
// 解析验证消息
{
if (!context.ModelState.IsValid)
{
var allValidationResults = new List<ValidationResult>();
int errorCount = 0;
//重新获取错误信息
foreach (var item in context.ActionArguments)
{
if (errorCount == context.ModelState.ErrorCount) break;
foreach (var parameter in context.ModelState)
{
if (errorCount == context.ModelState.ErrorCount) break;
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(item.Value!);
ValidateProperty(validationContext, validationResults, parameter.Key);
allValidationResults.AddRange(validationResults);
errorCount += validationResults.Count;
}
}
//var validationMetadata = GetValidationMetadata(context.ModelState!);
if (allValidationResults.Count > 0)
{
string? errorMessage;
if (allValidationResults.Count == 1)
{
errorMessage = allValidationResults.FirstOrDefault()!.ErrorMessage;
}
else
{
errorMessage = allValidationResults!.ToDictionary(a => a.MemberNames.FirstOrDefault()!, a => a.ErrorMessage).ToSystemTextJsonString();
}
var result = unifyResult.OnValidateFailed(context, errorMessage);
if (result != null)
{
context.Result = result;
// 存储验证执行结果
context.HttpContext.Items[ValidationFailedKey] = errorMessage;
return;
}
}
}
}
#endregion
// 执行 Action 并获取结果
ActionExecutedContext? actionExecutedContext = await next();
#region
// 如果出现异常
if (actionExecutedContext.Exception != null)
{
// 判断是否支持 MVC 规范化处理
if (UnifyContext.CheckHttpContextNonUnify(httpContext)) return;
// 执行规范化异常处理
actionExecutedContext.Result = unifyResult.OnException(actionExecutedContext);
actionExecutedContext.ExceptionHandled = true;
return;
}
#endregion
#region
// 处理已经含有状态码结果的 Result
if (actionExecutedContext.Result is IStatusCodeActionResult statusCodeResult && statusCodeResult.StatusCode != null)
{
// 小于 200 或者 大于 299 都不是成功值,直接跳过
if (statusCodeResult.StatusCode.Value < 200 || statusCodeResult.StatusCode.Value > 299)
{
// 处理规范化结果
if (!UnifyContext.CheckStatusCodeNonUnify(httpContext))
{
var statusCode = statusCodeResult.StatusCode.Value;
// 解决刷新 Token 时间和 Token 时间相近问题
if (statusCodeResult.StatusCode.Value == StatusCodes.Status401Unauthorized
&& httpContext.Response.Headers.ContainsKey("access-token")
&& httpContext.Response.Headers.ContainsKey("x-access-token"))
{
httpContext.Response.StatusCode = statusCode = StatusCodes.Status403Forbidden;
}
// 如果 Response 已经完成输出,则禁止写入
if (httpContext.Response.HasStarted) return;
await unifyResult.OnResponseStatusCodes(httpContext, statusCode);
}
return;
}
}
#endregion
#region
// 获取控制器信息
var actionDescriptor = actionExecutedContext.ActionDescriptor as ControllerActionDescriptor;
// 判断是否支持 MVC 规范化处理或特定检查
if (UnifyContext.CheckHttpContextNonUnify(httpContext)) return;
// 判断是否跳过规范化处理
if (UnifyContext.CheckSucceededNonUnify(actionDescriptor!.MethodInfo)) return;
// 处理 BadRequestObjectResult 类型规范化处理
if (actionExecutedContext.Result is BadRequestObjectResult badRequestObjectResult)
{
// 解析验证消息
var validationMetadata = GetValidationMetadata(badRequestObjectResult.Value!);
var result = unifyResult.OnValidateFailed(context, validationMetadata);
if (result != null) actionExecutedContext.Result = result;
}
else
{
IActionResult? result = default;
// 检查是否是有效的结果(可进行规范化的结果)
if (UnifyContext.CheckVaildResult(actionExecutedContext.Result!, out var data))
{
result = unifyResult.OnSucceeded(actionExecutedContext, data);
}
// 如果是不能规范化的结果类型,则跳过
if (result == null) return;
actionExecutedContext.Result = result;
}
#endregion
}
/// <summary>
/// 获取验证错误信息
/// </summary>
/// <param name="errors"></param>
/// <returns></returns>
internal static string GetValidationMetadata(object errors)
{
object? validationResults = null;
string? message = default;
// 判断是否是集合类型
if (errors is IEnumerable && errors is not string)
{
// 如果是模型验证字典类型
if (errors is ModelStateDictionary modelState)
{
// 将验证错误信息转换成字典并序列化成 Json
validationResults = modelState.Where(u => modelState[u.Key]!.ValidationState == ModelValidationState.Invalid)
.ToDictionary(u => u.Key, u => modelState[u.Key]!.Errors.Select(c => c.ErrorMessage).ToArray());
}
// 如果是 ValidationProblemDetails 特殊类型
else if (errors is ValidationProblemDetails validation)
{
validationResults = validation.Errors
.ToDictionary(u => u.Key, u => u.Value.ToArray());
}
// 如果是字典类型
else if (errors is IDictionary<string, string[]> dicResults)
{
validationResults = dicResults;
}
message = JsonSerializer.Serialize(validationResults, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
});
}
return message;
}
/// <summary>
/// 获取异常元数据
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetExceptionMetadata(ActionContext context)
{
// 判断是否是 ExceptionContext 或者 ActionExecutedContext
var exception = context is ExceptionContext exContext
? exContext.Exception
: (
context is ActionExecutedContext edContext
? edContext.Exception
: default
);
string? errors = exception?.InnerException?.Message ?? exception?.Message;
return errors;
}
/// <summary>
/// 验证整个模型时验证属性方法
/// </summary>
/// <param name="context"></param>
/// <param name="results"></param>
/// <param name="pName"></param>
private void ValidateProperty(ValidationContext context, List<ValidationResult> results, string pName)
{
// 获得所有可写属性
var pi = context.ObjectType.GetPropertyByName(pName);
if (pi != null)
{
// 设置其关联属性字段
var propertyValue = pi.GetValue(context.ObjectInstance);
var fieldIdentifier = new FieldIdentifier(context.ObjectInstance, pi.Name);
context.DisplayName = fieldIdentifier.GetDisplayName();
context.MemberName = fieldIdentifier.FieldName;
// 组件进行验证
ValidateDataAnnotations(propertyValue, context, results, pi);
}
}
/// <summary>
/// 通过属性设置的 DataAnnotation 进行数据验证
/// </summary>
/// <param name="value"></param>
/// <param name="context"></param>
/// <param name="results"></param>
/// <param name="propertyInfo"></param>
/// <param name="memberName"></param>
private void ValidateDataAnnotations(object? value, ValidationContext context, List<ValidationResult> results, PropertyInfo propertyInfo, string? memberName = null)
{
var rules = propertyInfo.GetCustomAttributes(true).OfType<ValidationAttribute>().ToList();
var metadataType = context.ObjectType.GetCustomAttribute<MetadataTypeAttribute>(false);
if (metadataType != null)
{
var p = metadataType.MetadataClassType.GetPropertyByName(propertyInfo.Name);
if (p != null)
{
rules.AddRange(p.GetCustomAttributes(true).OfType<ValidationAttribute>());
}
}
var displayName = context.DisplayName;
memberName ??= propertyInfo.Name;
var attributeSpan = nameof(Attribute).AsSpan();
foreach (var rule in rules)
{
var result = rule.GetValidationResult(value, context);
if (result != null && result != ValidationResult.Success)
{
// 查找 resx 资源文件中的 ErrorMessage
var ruleNameSpan = rule.GetType().Name.AsSpan();
var index = ruleNameSpan.IndexOf(attributeSpan, StringComparison.OrdinalIgnoreCase);
var ruleName = ruleNameSpan[..index];
var find = false;
// 通过设置 ErrorMessage 检索
if (!context.ObjectType.Assembly.IsDynamic && !find
&& !string.IsNullOrEmpty(rule.ErrorMessage)
&& App.CreateLocalizerByType(context.ObjectType)!.TryGetLocalizerString(rule.ErrorMessage, out var msg))
{
rule.ErrorMessage = msg;
find = true;
}
// 通过 Attribute 检索
if (!rule.GetType().Assembly.IsDynamic && !find
&& App.CreateLocalizerByType(rule.GetType())!.TryGetLocalizerString(nameof(rule.ErrorMessage), out msg))
{
rule.ErrorMessage = msg;
find = true;
}
// 通过 字段.规则名称 检索
if (!context.ObjectType.Assembly.IsDynamic && !find
&& App.CreateLocalizerByType(context.ObjectType)!.TryGetLocalizerString($"{memberName}.{ruleName.ToString()}", out msg))
{
rule.ErrorMessage = msg;
find = true;
}
if (!find)
{
rule.ErrorMessage = result.ErrorMessage;
}
var errorMessage = !string.IsNullOrEmpty(rule.ErrorMessage) && rule.ErrorMessage.Contains("{0}")
? rule.FormatErrorMessage(displayName)
: rule.ErrorMessage;
results.Add(new ValidationResult(errorMessage, new string[] { memberName }));
}
}
}
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,47 +9,50 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 权限按钮服务
/// 规范化结果提供器
/// </summary>
public interface IButtonService : ISugarService, ITransient
public interface IUnifyResultProvider
{
/// <summary>
/// 添加按钮
/// 异常返回值
/// </summary>
/// <param name="input">添加参数</param>
/// <param name="context"></param>
/// <returns></returns>
Task AddAsync(ButtonAddInput input);
IActionResult OnException(ActionExecutedContext context);
/// <summary>
/// 批量添加
/// 成功返回值
/// </summary>
/// <param name="input"></param>
/// <param name="context"></param>
/// <param name="data"></param>
/// <returns></returns>
Task<List<long>> AddBatchAsync(ButtonAddInput input);
IActionResult OnSucceeded(ActionExecutedContext context, object? data);
/// <summary>
/// 删除按钮
/// 验证失败返回值
/// </summary>
/// <param name="input">删除参数</param>
/// <param name="context"></param>
/// <param name="errors"></param>
/// <returns></returns>
Task DeleteAsync(List<BaseIdInput> input);
IActionResult OnValidateFailed(ActionExecutingContext context, string? errors);
/// <summary>
/// 编辑按钮
/// 拦截返回状态码
/// </summary>
/// <param name="input">编辑参数</param>
/// <param name="context"></param>
/// <param name="statusCode"></param>
/// <returns></returns>
Task EditAsync(ButtonEditInput input);
/// <summary>
/// 按钮分页查询
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>按钮分页列表</returns>
Task<SqlSugarPagedList<SysResource>> PageAsync(ButtonPageInput input);
Task OnResponseStatusCodes(HttpContext context, int statusCode);
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,11 +9,17 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
global using Furion.DynamicApiController;
global using System;
global using System.Threading.Tasks;
global using ThingsGateway.Admin.Application;
// 版权归百小僧及百签科技(广东)有限公司所有。
namespace Microsoft.AspNetCore.Mvc;
/// <summary>
/// 禁止规范化处理
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class NonUnifyAttribute : Attribute
{
}

View File

@@ -0,0 +1,149 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Reflection;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 规范化结果上下文
/// </summary>
public static class UnifyContext
{
/// <summary>
/// 是否启用规范化结果
/// </summary>
internal static bool EnabledUnifyHandler = true;
/// <summary>
/// 检查请求成功是否进行规范化处理
/// </summary>
/// <param name="method"></param>
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
internal static bool CheckSucceededNonUnify(MethodInfo method)
{
// 判断是否跳过规范化处理
var isSkip = !EnabledUnifyHandler
|| method.CustomAttributes.Any(x => typeof(NonUnifyAttribute).IsAssignableFrom(x.AttributeType) || typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType))
|| method.ReflectedType!.IsDefined(typeof(NonUnifyAttribute), true)
|| method.DeclaringType!.Assembly.GetName().Name!.StartsWith("Microsoft.AspNetCore.OData");
return isSkip;
}
/// <summary>
/// 检查短路状态码(>=400是否进行规范化处理
/// </summary>
/// <param name="context"></param>
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
internal static bool CheckStatusCodeNonUnify(HttpContext context)
{
// 获取终点路由特性
var endpointFeature = context.Features.Get<IEndpointFeature>();
if (endpointFeature == null) return false;
// 判断是否跳过规范化处理
var isSkip = !EnabledUnifyHandler
|| context.Request.Headers["accept"].ToString().Contains("odata.metadata=", StringComparison.OrdinalIgnoreCase)
|| context.Request.Headers["accept"].ToString().Contains("odata.streaming=", StringComparison.OrdinalIgnoreCase)
|| ResponseContentTypesOfNonUnify.Any(u => context.Response.Headers["content-type"].ToString().Contains(u, StringComparison.OrdinalIgnoreCase)
|| context.GetMetadata<NonUnifyAttribute>() != null
|| endpointFeature?.Endpoint?.Metadata?.GetMetadata<NonUnifyAttribute>() != null
);
return isSkip;
}
/// <summary>
/// 跳过规范化处理的 Response Content-Type
/// </summary>
internal static string[] ResponseContentTypesOfNonUnify = new[]
{
"text/event-stream",
"application/pdf",
"application/octet-stream",
"image/"
};
/// <summary>
/// 检查 HttpContext 是否进行规范化处理
/// </summary>
/// <param name="httpContext"></param>
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
internal static bool CheckHttpContextNonUnify(HttpContext httpContext)
{
var contentType = httpContext.Response.Headers["content-type"].ToString();
if (ResponseContentTypesOfNonUnify.Any(u => contentType.Contains(u, StringComparison.OrdinalIgnoreCase)))
{
return true;
}
return false;
}
/// <summary>
/// 检查是否是有效的结果(可进行规范化的结果)
/// </summary>
/// <param name="result"></param>
/// <param name="data"></param>
/// <returns></returns>
internal static bool CheckVaildResult(IActionResult result, out object? data)
{
data = default;
// 排除以下结果,跳过规范化处理
var isDataResult = result switch
{
ViewResult => false,
PartialViewResult => false,
FileResult => false,
ChallengeResult => false,
SignInResult => false,
SignOutResult => false,
RedirectToPageResult => false,
RedirectToRouteResult => false,
RedirectResult => false,
RedirectToActionResult => false,
LocalRedirectResult => false,
ForbidResult => false,
ViewComponentResult => false,
PageResult => false,
NotFoundResult => false,
NotFoundObjectResult => false,
_ => true,
};
// 目前支持返回值 ActionResult
if (isDataResult) data = result switch
{
// 处理内容结果
ContentResult content => content.Content,
// 处理对象结果
ObjectResult obj => obj.Value,
// 处理 JSON 对象
JsonResult json => json.Value,
_ => null,
};
return isDataResult;
}
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,9 +9,10 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 全局返回结果
@@ -28,17 +28,12 @@ public class UnifyResult<T>
/// <summary>
/// 数据
/// </summary>
public T Data { get; set; }
/// <summary>
/// 附加数据
/// </summary>
public object Extras { get; set; }
public T? Data { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public object Msg { get; set; }
public object? Msg { get; set; }
/// <summary>
/// 时间

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,33 +9,32 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.DataValidation;
using Furion.FriendlyException;
using Furion.UnifyResult;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Localization;
namespace ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 规范化RESTful风格返回值
/// </summary>
[SuppressSniffer, UnifyModel(typeof(UnifyResult<>))]
public class UnifyResultProvider : IUnifyResultProvider
{
private static IStringLocalizer Localizer = App.CreateLocalizerByType(typeof(UnifyResultProvider))!;
/// <summary>
/// 异常返回
/// </summary>
/// <param name="context"></param>
/// <param name="metadata"></param>
/// <returns></returns>
public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata)
public IActionResult OnException(ActionExecutedContext context)
{
return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors));
return new JsonResult(RESTfulResult(context.Result is IStatusCodeActionResult statusCodeResult ? statusCodeResult.StatusCode ?? 500 : 500, false, null, context.Exception?.GetTrue()?.Message));
}
/// <summary>
@@ -44,24 +42,18 @@ public class UnifyResultProvider : IUnifyResultProvider
/// </summary>
/// <param name="context"></param>
/// <param name="statusCode"></param>
/// <param name="unifyResultSettings"></param>
/// <returns></returns>
public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings = null)
public async Task OnResponseStatusCodes(HttpContext context, int statusCode)
{
// 设置响应状态码
UnifyContext.SetResponseStatusCodes(context, statusCode, unifyResultSettings);
switch (statusCode)
{
// 处理 401 状态码
case StatusCodes.Status401Unauthorized:
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "登录已过期,请重新登录"),
App.GetOptions<JsonOptions>()?.JsonSerializerOptions);
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, false, Localizer["TokenOver"]));
break;
// 处理 403 状态码
case StatusCodes.Status403Forbidden:
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "禁止访问,没有权限"),
App.GetOptions<JsonOptions>()?.JsonSerializerOptions);
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, false, default, "NoPermission"));
break;
default: break;
@@ -74,7 +66,7 @@ public class UnifyResultProvider : IUnifyResultProvider
/// <param name="context"></param>
/// <param name="data"></param>
/// <returns></returns>
public IActionResult OnSucceeded(ActionExecutedContext context, object data)
public IActionResult OnSucceeded(ActionExecutedContext context, object? data)
{
return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data));
}
@@ -83,11 +75,11 @@ public class UnifyResultProvider : IUnifyResultProvider
/// 验证失败返回
/// </summary>
/// <param name="context"></param>
/// <param name="metadata"></param>
/// <param name="errors"></param>
/// <returns></returns>
public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata)
public IActionResult OnValidateFailed(ActionExecutingContext context, string? errors)
{
return new JsonResult(RESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, errors: metadata.FirstErrorMessage ?? metadata.Message));
return new JsonResult(RESTfulResult(StatusCodes.Status400BadRequest, false, null, errors));
}
/// <summary>
@@ -98,14 +90,13 @@ public class UnifyResultProvider : IUnifyResultProvider
/// <param name="data">数据</param>
/// <param name="errors">错误信息</param>
/// <returns></returns>
private static UnifyResult<object> RESTfulResult(int statusCode, bool succeeded = default, object data = default, object errors = default)
private static UnifyResult<object> RESTfulResult(int statusCode, bool succeeded = default, object? data = default, object? errors = default)
{
return new UnifyResult<object>
{
Code = statusCode,
Msg = statusCode == StatusCodes.Status200OK ? "请求成功" : errors,
Msg = statusCode == StatusCodes.Status200OK ? "Success" : errors,
Data = data,
Extras = UnifyContext.Take(),
Time = DateTime.Now,
};
}

View File

@@ -0,0 +1,86 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using ThingsGateway.Core;
using RouteAttribute = Microsoft.AspNetCore.Mvc.RouteAttribute;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// Gitee WebHook
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class GiteeController : ControllerBase
{
/// <summary>
/// Gitee Webhook
/// </summary>
/// <returns></returns>
[HttpPost]
public IActionResult Webhook([FromQuery] string? id, [FromServices] IConfiguration config, [FromServices] IDispatchService<GiteePostBody> dispatch, [FromBody] GiteePostBody payload)
{
bool ret = false;
if (Check())
{
// 全局推送
if (payload.HeadCommit != null || payload.Commits?.Count > 0)
{
dispatch.Dispatch(new DispatchEntry<GiteePostBody>()
{
Name = "Gitee",
Entry = payload
});
}
ret = true;
}
return ret ? Ok() : Unauthorized();
bool Check()
{
var configId = config.GetValue<string>("WebHooks:Gitee:Id");
var configToken = config.GetValue<string>("WebHooks:Gitee:Token");
var token = "";
if (Request.Headers.TryGetValue("X-Gitee-Token", out var val))
{
token = val.FirstOrDefault() ?? string.Empty;
}
return id == configId && token == configToken
&& payload.Id == configId && payload.Password == configToken;
}
}
/// <summary>
/// Webhook 测试接口
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Webhook()
{
return Ok(new { Message = "Ok" });
}
/// <summary>
/// 跨域握手协议
/// </summary>
/// <returns></returns>
[HttpOptions]
public string Options() => string.Empty;
}

View File

@@ -0,0 +1,51 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
namespace ThingsGateway.Admin.Application;
[Route("openapi/auth")]
[LoggingMonitor]
public class OpenApiController : ControllerBase
{
private readonly IAuthService _authService;
public OpenApiController(IAuthService authService)
{
_authService = authService;
}
[HttpPost("login")]
[AllowAnonymous]
public Task<LoginOutput> LoginAsync([FromBody] LoginInput input)
{
return _authService.LoginAsync(input, false);
}
[HttpGet("logout")]
[Authorize]
[IgnoreRolePermission]
public async Task<IActionResult> LogoutAsync([FromQuery] string returnUrl)
{
await _authService.LoginOutAsync();
return Redirect(QueryHelpers.AddQueryString(Request.PathBase + CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
{
["ReturnUrl"] = returnUrl
}));
}
}

View File

@@ -0,0 +1,32 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ThingsGateway.Admin.Application;
[Route("api/[controller]/[action]")]
[RolePermission]
[Authorize(AuthenticationSchemes = "Bearer")]
public class TestController : ControllerBase
{
[HttpGet]
public async Task Test(QueryPageOptions queryPageOptions)
{
await Task.CompletedTask;
}
}

View File

@@ -1,76 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 会话信息
/// </summary>
public class VerificatInfo : PrimaryIdEntity
{
/// <summary>
/// 客户端ID列表
/// </summary>
[Newtonsoft.Json.JsonIgnore]
public List<string> ClientIds { get; set; } = new List<string>();
/// <summary>
/// 设备
/// </summary>
[Description("设备")]
[DataTable(Order = 1, IsShow = true, Sortable = true)]
public string Device { get; set; }
/// <summary>
/// 过期时间
/// </summary>
[Description("过期时间")]
public int Expire { get; set; }
/// <summary>
/// 验证Id
/// </summary>
[Description("验证Id")]
[DataTable(Order = 3, IsShow = true, Sortable = true)]
public override long Id { get; set; }
/// <summary>
/// 在线状态
/// </summary>
[Description("在线状态")]
[DataTable(Order = 2, IsShow = true, Sortable = true)]
public bool IsOnline => ClientIds.Count > 0;
/// <summary>
/// 连接数量
/// </summary>
[Description("连接数量")]
[DataTable(Order = 4, IsShow = true, Sortable = true)]
public int OnlineNum => ClientIds.Count;
/// <summary>
/// verificat剩余有效期
/// </summary>
[Description("有效期")]
[DataTable(Order = 5, IsShow = true, Sortable = true)]
public string VerificatRemain { get; set; }
/// <summary>
/// 超时时间
/// </summary>
[Description("超时时间")]
public DateTime VerificatTimeout { get; set; }
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,9 +9,18 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Core;
using BootstrapBlazor.Components;
using SqlSugar;
using System.Diagnostics.CodeAnalysis;
using ThingsGateway.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 主键id基类
@@ -24,6 +32,7 @@ public abstract class PrimaryIdEntity : IPrimaryIdEntity
/// </summary>
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
[IgnoreExcel]
[AutoGenerateColumn(Visible = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)]
public virtual long Id { get; set; }
}
@@ -37,6 +46,7 @@ public abstract class PrimaryKeyEntity : PrimaryIdEntity
/// </summary>
[SugarColumn(ColumnDescription = "扩展信息", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual string? ExtJson { get; set; }
}
@@ -50,6 +60,7 @@ public abstract class BaseEntity : PrimaryKeyEntity
/// </summary>
[SugarColumn(ColumnDescription = "创建时间", IsOnlyIgnoreUpdate = true, IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual DateTime? CreateTime { get; set; }
/// <summary>
@@ -57,13 +68,16 @@ public abstract class BaseEntity : PrimaryKeyEntity
/// </summary>
[SugarColumn(ColumnDescription = "创建人", IsOnlyIgnoreUpdate = true, IsNullable = true)]
[IgnoreExcel]
public virtual string CreateUser { get; set; }
[NotNull]
[AutoGenerateColumn(Ignore = true)]
public virtual string? CreateUser { get; set; }
/// <summary>
/// 创建者Id
/// </summary>
[SugarColumn(ColumnDescription = "创建者Id", IsOnlyIgnoreUpdate = true, IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual long CreateUserId { get; set; }
/// <summary>
@@ -71,6 +85,7 @@ public abstract class BaseEntity : PrimaryKeyEntity
/// </summary>
[SugarColumn(ColumnDescription = "软删除", IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual bool IsDelete { get; set; } = false;
/// <summary>
@@ -78,6 +93,7 @@ public abstract class BaseEntity : PrimaryKeyEntity
/// </summary>
[SugarColumn(ColumnDescription = "更新时间", IsOnlyIgnoreInsert = true, IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual DateTime? UpdateTime { get; set; }
/// <summary>
@@ -85,19 +101,21 @@ public abstract class BaseEntity : PrimaryKeyEntity
/// </summary>
[SugarColumn(ColumnDescription = "更新人", IsOnlyIgnoreInsert = true, IsNullable = true)]
[IgnoreExcel]
public virtual string UpdateUser { get; set; }
[AutoGenerateColumn(Ignore = true)]
public virtual string? UpdateUser { get; set; }
/// <summary>
/// 修改者Id
/// </summary>
[SugarColumn(ColumnDescription = "修改者Id", IsOnlyIgnoreInsert = true, IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual long? UpdateUserId { get; set; }
/// <summary>
/// 排序码
///</summary>
[SugarColumn(ColumnDescription = "排序码", IsNullable = false)]
[IgnoreExcel]
public virtual int SortCode { get; set; }
}
[SugarColumn(ColumnDescription = "排序码", IsNullable = true)]
[AutoGenerateColumn(Visible = false, DefaultSort = true, Sortable = true, DefaultSortOrder = SortOrder.Asc)]
public int? SortCode { get; set; }
}

View File

@@ -1,50 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 配置
///</summary>
[SugarTable("sys_config", TableDescription = "配置表")]
[Tenant(SqlSugarConst.DB_Admin)]
public class SysConfig : BaseEntity
{
/// <summary>
/// 分类
///</summary>
[SugarColumn(ColumnDescription = "分类", Length = 200)]
public virtual string Category { get; set; }
/// <summary>
/// 配置键
///</summary>
[SugarColumn(ColumnDescription = "配置键", Length = 200)]
[DataTable(Order = 1, IsShow = true, Sortable = true)]
public virtual string ConfigKey { get; set; }
/// <summary>
/// 配置值
///</summary>
[SugarColumn(ColumnDescription = "配置值", ColumnDataType = StaticConfig.CodeFirst_BigString)]
[DataTable(Order = 2, IsShow = true, Sortable = true, CellClass = " table-text-truncate ")]
public virtual string ConfigValue { get; set; }
/// <summary>
/// 备注
///</summary>
[SugarColumn(ColumnDescription = "备注", Length = 200, IsNullable = true)]
[DataTable(Order = 4, IsShow = true)]
public string Remark { get; set; }
}

View File

@@ -0,0 +1,63 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
[SugarTable("sys_dict", TableDescription = "字典表")]
[Tenant(SqlSugarConst.DB_Admin)]
public class SysDict : BaseEntity
{
/// <summary>
/// 类型
///</summary>
[SugarColumn(ColumnDescription = "类型", Length = 200)]
[AutoGenerateColumn(Ignore = true, Filterable = true, Sortable = true)]
public virtual DictTypeEnum DictType { get; set; }
/// <summary>
/// 分类
///</summary>
[SugarColumn(ColumnDescription = "分类", Length = 200)]
[Required]
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
public string Category { get; set; }
/// <summary>
/// 名称
///</summary>
[SugarColumn(ColumnDescription = "名称", Length = 200)]
[Required]
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
public virtual string Name { get; set; }
/// <summary>
/// 代码
///</summary>
[SugarColumn(ColumnDescription = "代码", ColumnDataType = StaticConfig.CodeFirst_BigString)]
[Required]
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
public virtual string Code { get; set; }
/// <summary>
/// 描述
///</summary>
[SugarColumn(ColumnDescription = "描述", Length = 200, IsNullable = true)]
public string Remark { get; set; }
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,7 +9,12 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BootstrapBlazor.Components;
using SqlSugar;
namespace ThingsGateway.Admin.Application;
@@ -19,54 +23,118 @@ namespace ThingsGateway.Admin.Application;
///</summary>
[SugarTable("sys_operatelog", TableDescription = "操作日志表")]
[Tenant(SqlSugarConst.DB_Log)]
public class SysOperateLog : SysVisitLog
public class SysOperateLog
{
/// <summary>
/// 日志分类
///</summary>
[SugarColumn(ColumnDescription = "日志分类", Length = 200)]
[AutoGenerateColumn(Order = 1, Filterable = true, Sortable = true)]
public LogCateGoryEnum Category { get; set; }
/// <summary>
/// 日志名称
///</summary>
[SugarColumn(ColumnDescription = "日志名称", Length = 200)]
[AutoGenerateColumn(Order = 2, Filterable = true, Sortable = true)]
public string Name { get; set; }
/// <summary>
/// 类名称
///</summary>
[SugarColumn(ColumnDescription = "类名称", Length = 200)]
[DataTable(Order = 21, IsShow = true, Sortable = true, DefaultFilter = true, CellClass = " table-text-truncate ")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string ClassName { get; set; }
/// <summary>
/// 具体消息
///</summary>
[SugarColumn(ColumnDescription = "具体消息", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[DataTable(Order = 22, IsShow = true, Sortable = true, DefaultFilter = false, CellClass = " table-text-truncate ")]
public string ExeMessage { get; set; }
/// <summary>
/// 方法名称
///</summary>
[SugarColumn(ColumnDescription = "方法名称", Length = 200)]
[DataTable(Order = 23, IsShow = true, Sortable = true, DefaultFilter = true, CellClass = " table-text-truncate ")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string MethodName { get; set; }
/// <summary>
/// 请求参数
///</summary>
[SugarColumn(ColumnDescription = "请求参数", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[DataTable(Order = 24, IsShow = true, Sortable = true, DefaultFilter = false, CellClass = " table-text-truncate ")]
public string ParamJson { get; set; }
[AutoGenerateColumn(ShowTips = true, Filterable = true, Sortable = true)]
public string? ParamJson { get; set; }
/// <summary>
/// 请求方式
///</summary>
[SugarColumn(ColumnDescription = "请求方式", Length = 200, IsNullable = true)]
[DataTable(Order = 25, IsShow = true, Sortable = true, DefaultFilter = true, CellClass = " table-text-truncate ")]
public string ReqMethod { get; set; }
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string? ReqMethod { get; set; }
/// <summary>
/// 请求地址
///</summary>
[SugarColumn(ColumnDescription = "请求地址", ColumnDataType = StaticConfig.CodeFirst_BigString)]
[DataTable(Order = 26, IsShow = true, Sortable = true, DefaultFilter = true, CellClass = " table-text-truncate ")]
public string ReqUrl { get; set; }
[SugarColumn(ColumnDescription = "请求地址", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string? ReqUrl { get; set; }
/// <summary>
/// 返回结果
///</summary>
[SugarColumn(ColumnDescription = "返回结果", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[DataTable(Order = 27, IsShow = true, Sortable = true, DefaultFilter = false, CellClass = " table-text-truncate ")]
public string ResultJson { get; set; }
[AutoGenerateColumn(ShowTips = true, Filterable = true, Sortable = true)]
public string? ResultJson { get; set; }
/// <summary>
/// 具体消息
///</summary>
[SugarColumn(ColumnDescription = "具体消息", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[AutoGenerateColumn(ShowTips = true, Filterable = true, Sortable = true)]
public string? ExeMessage { get; set; }
/// <summary>
/// 执行状态
///</summary>
[SugarColumn(ColumnDescription = "执行状态", Length = 200)]
[AutoGenerateColumn(Filterable = true, Sortable = true)]
public bool ExeStatus { get; set; }
/// <summary>
/// 操作账号
///</summary>
[SugarColumn(ColumnDescription = "操作账号", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Filterable = true, Sortable = true)]
public string? OpAccount { get; set; }
/// <summary>
/// 操作浏览器
///</summary>
[SugarColumn(ColumnDescription = "操作浏览器", Length = 200)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string OpBrowser { get; set; }
/// <summary>
/// 操作ip
///</summary>
[SugarColumn(ColumnDescription = "操作ip", Length = 200)]
[AutoGenerateColumn(Filterable = true, Sortable = true)]
public string? OpIp { get; set; }
/// <summary>
/// 操作系统
///</summary>
[SugarColumn(ColumnDescription = "操作系统", Length = 200)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string OpOs { get; set; }
/// <summary>
/// 操作时间
///</summary>
[SugarColumn(ColumnDescription = "操作时间")]
[AutoGenerateColumn(Visible = true, DefaultSort = true, Sortable = true, DefaultSortOrder = SortOrder.Desc)]
public DateTime OpTime { get; set; }
/// <summary>
/// 验证Id
///</summary>
[SugarColumn(ColumnDescription = "验证Id")]
[IgnoreExcel]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public long VerificatId { get; set; }
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,7 +9,10 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using SqlSugar;
namespace ThingsGateway.Admin.Application;
@@ -25,7 +27,7 @@ public class SysRelation : PrimaryKeyEntity
/// 分类
///</summary>
[SugarColumn(ColumnDescription = "分类", Length = 200)]
public string Category { get; set; }
public RelationCategoryEnum Category { get; set; }
/// <summary>
/// 对象ID
@@ -37,5 +39,5 @@ public class SysRelation : PrimaryKeyEntity
/// 目标ID
///</summary>
[SugarColumn(ColumnDescription = "目标ID", IsNullable = true)]
public string TargetId { get; set; }
public string? TargetId { get; set; }
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,9 +9,18 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components.Routing;
using Newtonsoft.Json;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
@@ -26,91 +34,72 @@ public class SysResource : BaseEntity
/// <summary>
/// 父id
///</summary>
[SugarColumn(ColumnDescription = "父id", IsNullable = true)]
public virtual long? ParentId { get; set; }
[SugarColumn(ColumnDescription = "父id")]
[AutoGenerateColumn(Ignore = true)]
public virtual long ParentId { get; set; } = 0;
/// <summary>
/// 模块
///</summary>
[SugarColumn(ColumnDescription = "模块")]
[AutoGenerateColumn(Visible = false, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false, Searchable = true)]
public virtual long Module { get; set; }
/// <summary>
/// 标题
///</summary>
[SugarColumn(ColumnDescription = "标题", Length = 200)]
[DataTable(Order = 1, IsShow = true, Sortable = true)]
[Required]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true, Searchable = true)]
public virtual string Title { get; set; }
/// <summary>
/// 编码
///</summary>
[SugarColumn(ColumnDescription = "编码", Length = 200, IsNullable = true)]
[DataTable(Order = 5, IsShow = true, Sortable = true, DefaultFilter = true)]
public virtual string Code { get; set; }
/// <summary>
/// 分类
///</summary>
[SugarColumn(ColumnDescription = "分类", Length = 200)]
[DataTable(Order = 4, IsShow = true, Sortable = true, DefaultFilter = true)]
public string Category { get; set; }
/// <summary>
/// 菜单类型
///</summary>
[SugarColumn(ColumnDescription = "菜单类型", ColumnDataType = "varchar(50)")]
[DataTable(Order = 4, IsShow = true, Sortable = true, DefaultFilter = true)]
public virtual MenuTypeEnum MenuType { get; set; }
/// <summary>
/// 路径
///</summary>
[SugarColumn(ColumnDescription = "路径", Length = 200, IsNullable = true)]
[DataTable(Order = 3, IsShow = true, Sortable = true)]
public virtual string Href { get; set; }
/// <summary>
/// 图标
///</summary>
[SugarColumn(ColumnDescription = "图标", Length = 200, IsNullable = true)]
[DataTable(Order = 2, IsShow = true, Sortable = true)]
public virtual string Icon { get; set; }
[AutoGenerateColumn(Visible = true, Sortable = false, Filterable = false)]
public virtual string? Icon { get; set; }
/// <summary>
/// 是否隐藏
/// 编码
///</summary>
[SugarColumn(ColumnDescription = "编码", Length = 200)]
[Required]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public virtual string Code { get; set; }
/// <summary>
/// 分类
///</summary>
[SugarColumn(ColumnDescription = "分类")]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public ResourceCategoryEnum Category { get; set; } = ResourceCategoryEnum.Menu;
/// <summary>
/// 目标类型
///</summary>
[SugarColumn(ColumnDescription = "目标类型", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual TargetEnum? Target { get; set; }
/// <summary>
/// 菜单匹配类型
/// </summary>
[SugarColumn(ColumnDescription = "隐藏", Length = 200)]
[DataTable(Order = 2, IsShow = true, Sortable = true)]
public bool Hidden { get; set; } = false;
[SugarColumn(ColumnDescription = "菜单匹配类型", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual NavLinkMatch? NavLinkMatch { get; set; }
/// <summary>
/// 路径
///</summary>
[SugarColumn(ColumnDescription = "路径", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true, Searchable = true)]
public virtual string Href { get; set; }
/// <summary>
/// 子节点
/// </summary>
[SugarColumn(IsIgnore = true)]
public List<SysResource> Children { get; set; }
}
/// <summary>
/// 链接跳转类型
/// </summary>
public enum MenuTypeEnum
{
/// <summary>
/// 目录
/// </summary>
[Description("目录")]
CATALOG = 0,
/// <summary>
/// 组件
/// </summary>
[Description("组件")]
MENU,
/// <summary>
/// 内链
/// </summary>
[Description("内链")]
IFRAME,
/// <summary>
/// 外链
/// </summary>
[Description("外链")]
LINK,
[AutoGenerateColumn(Ignore = true)]
public List<SysResource>? Children { get; set; }
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,7 +9,14 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BootstrapBlazor.Components;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
@@ -25,35 +31,26 @@ public class SysRole : BaseEntity
/// 编码
///</summary>
[SugarColumn(ColumnDescription = "编码", Length = 200)]
[DataTable(Order = 2, IsShow = true, Sortable = true)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public string Code { get; set; }
/// <summary>
/// 名称
///</summary>
[SugarColumn(ColumnDescription = "名称", Length = 200)]
[DataTable(Order = 1, IsShow = true, Sortable = true)]
[Required]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual string Name { get; set; }
/// <summary>
/// 分类
///</summary>
[SugarColumn(ColumnDescription = "分类", Length = 200, IsNullable = false)]
[DataTable(Order = 1, IsShow = true, Sortable = true)]
public virtual string Category { get; set; }
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual RoleCategoryEnum Category { get; set; }
public override int GetHashCode()
{
return Id.GetHashCode();
}
public override bool Equals(object? obj)
{
if (obj == null || !(obj is UserSelectorOutput))
{
return false;
}
return Id == ((UserSelectorOutput)obj).Id;
}
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,7 +9,16 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BootstrapBlazor.Components;
using Mapster;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
@@ -21,111 +29,159 @@ namespace ThingsGateway.Admin.Application;
[Tenant(SqlSugarConst.DB_Admin)]
public class SysUser : BaseEntity
{
/// <summary>
/// 头像
///</summary>
[SugarColumn(ColumnDescription = "头像", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = false, Filterable = false)]
[AdaptIgnore]
public virtual string? Avatar { get; set; }
/// <summary>
/// 账号
///</summary>
[SugarColumn(ColumnDescription = "账号", Length = 200)]
[DataTable(Order = 1, IsShow = true, Sortable = true)]
[Required]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual string Account { get; set; }
/// <summary>
/// 按钮码集合
/// </summary>
[SugarColumn(IsIgnore = true)]
public List<string> ButtonCodeList { get; set; }
/// <summary>
/// 邮箱
///</summary>
[SugarColumn(ColumnDescription = "邮箱", Length = 200, IsNullable = true)]
[DataTable(Order = 2, IsShow = true, Sortable = true)]
public string Email { get; set; }
/// <summary>
/// 上次登录设备
///</summary>
[SugarColumn(ColumnDescription = "上次登录设备", IsNullable = true)]
[DataTable(Order = 3, IsShow = true, Sortable = true, DefaultFilter = true)]
public string LastLoginDevice { get; set; }
/// <summary>
/// 上次登录ip
///</summary>
[SugarColumn(ColumnDescription = "上次登录ip", Length = 200, IsNullable = true)]
[DataTable(Order = 4, IsShow = true, Sortable = true, DefaultFilter = true)]
public string LastLoginIp { get; set; }
/// <summary>
/// 上次登录时间
///</summary>
[SugarColumn(ColumnDescription = "上次登录时间", IsNullable = true)]
[DataTable(Order = 5, IsShow = true, Sortable = true, DefaultFilter = true)]
public DateTime? LastLoginTime { get; set; }
/// <summary>
/// 最新登录设备
///</summary>
[SugarColumn(ColumnDescription = "最新登录设备", IsNullable = true)]
[DataTable(Order = 6, IsShow = true, Sortable = true, DefaultFilter = false)]
public string LatestLoginDevice { get; set; }
/// <summary>
/// 最新登录ip
///</summary>
[SugarColumn(ColumnDescription = "最新登录ip", Length = 200, IsNullable = true)]
[DataTable(Order = 7, IsShow = true, Sortable = true, DefaultFilter = false)]
public string LatestLoginIp { get; set; }
/// <summary>
/// 最新登录时间
///</summary>
[SugarColumn(ColumnDescription = "最新登录时间", IsNullable = true)]
[DataTable(Order = 8, IsShow = true, Sortable = true, DefaultFilter = false)]
public DateTime? LatestLoginTime { get; set; }
/// <summary>
/// 密码
///</summary>
[SugarColumn(ColumnDescription = "密码", ColumnDataType = StaticConfig.CodeFirst_BigString)]
[AutoGenerateColumn(Ignore = true)]
public string Password { get; set; }
/// <summary>
/// 权限码集合
/// </summary>
[SugarColumn(IsIgnore = true)]
public List<string> PermissionCodeList { get; set; }
/// <summary>
/// 手机
///</summary>
[SugarColumn(ColumnDescription = "手机", Length = 200, IsNullable = true)]
[DataTable(Order = 2, IsShow = true, Sortable = true, DefaultFilter = false)]
public string Phone { get; set; }
/// <summary>
/// 数据范围集合
/// </summary>
[SugarColumn(IsIgnore = true)]
public List<DataScope> DataScopeList { get; set; }
/// <summary>
/// 角色码集合
/// </summary>
[SugarColumn(IsIgnore = true)]
public List<string> RoleCodeList { get; set; }
/// <summary>
/// 角色ID集合
/// </summary>
[SugarColumn(IsIgnore = true)]
public List<long> RoleIdList { get; set; }
/// <summary>
/// 是否启用
///</summary>
[SugarColumn(ColumnDescription = "是否启用")]
[DataTable(Order = 2, IsShow = true, Sortable = true, DefaultFilter = false)]
public bool UserStatus { get; set; }
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public bool Status { get; set; }
/// <summary>
/// 手机
/// 这里使用了SM4自动加密解密
///</summary>
[SugarColumn(ColumnDescription = "手机", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public string? Phone { get; set; }
/// <summary>
/// 邮箱
///</summary>
[SugarColumn(ColumnDescription = "邮箱", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public string? Email { get; set; }
/// <summary>
/// 上次登录ip
///</summary>
[SugarColumn(ColumnDescription = "上次登录ip", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public string? LastLoginIp { get; set; }
/// <summary>
/// 上次登录设备
///</summary>
[SugarColumn(ColumnDescription = "上次登录设备", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public string? LastLoginDevice { get; set; }
/// <summary>
/// 上次登录时间
///</summary>
[SugarColumn(ColumnDescription = "上次登录时间", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public DateTime? LastLoginTime { get; set; }
/// <summary>
/// 上次登录地点
///</summary>
[SugarColumn(ColumnDescription = "上次登录地点", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public string LastLoginAddress { get; set; }
/// <summary>
/// 最新登录ip
///</summary>
[SugarColumn(ColumnDescription = "最新登录ip", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public string? LatestLoginIp { get; set; }
/// <summary>
/// 最新登录时间
///</summary>
[SugarColumn(ColumnDescription = "最新登录时间", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public DateTime? LatestLoginTime { get; set; }
/// <summary>
/// 最新登录设备
///</summary>
[SugarColumn(ColumnDescription = "最新登录设备", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public string? LatestLoginDevice { get; set; }
/// <summary>
/// 最新登录地点
///</summary>
[SugarColumn(ColumnDescription = "最新登录地点", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public string LatestLoginAddress { get; set; }
/// <summary>
/// 默认模块
///</summary>
[SugarColumn(ColumnDescription = "默认模块")]
[AutoGenerateColumn(Ignore = true)]
public long DefaultModule { get; set; }
#region other
/// <summary>
/// 按钮码集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public Dictionary<string, List<string>> ButtonCodeList { get; set; } = new();
/// <summary>
/// 权限码集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public IEnumerable<string> PermissionCodeList { get; set; } = Enumerable.Empty<string>();
/// <summary>
/// 角色码集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public IEnumerable<string> RoleCodeList { get; set; } = Enumerable.Empty<string>();
/// <summary>
/// 角色ID集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public IEnumerable<long> RoleIdList { get; set; } = Enumerable.Empty<long>();
/// <summary>
/// 数据范围集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public IEnumerable<DataScope> DataScopeList { get; set; } = Enumerable.Empty<DataScope>();
/// <summary>
/// 模块集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public IEnumerable<SysResource> ModuleList { get; set; } = Enumerable.Empty<SysResource>();
#endregion other
}
/// <summary>

View File

@@ -1,87 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 访问日志表
///</summary>
[SugarTable("sys_visitlog", TableDescription = "访问日志表")]
[Tenant(SqlSugarConst.DB_Log)]
public class SysVisitLog : PrimaryIdEntity
{
/// <summary>
/// 日志分类
///</summary>
[SugarColumn(ColumnDescription = "日志分类", Length = 200)]
[DataTable(Order = 1, IsShow = true, Sortable = true, DefaultFilter = false)]
public string Category { get; set; }
/// <summary>
/// 执行状态
///</summary>
[SugarColumn(ColumnDescription = "执行状态", Length = 200)]
[DataTable(Order = 3, IsShow = true, Sortable = true, DefaultFilter = false)]
public string ExeStatus { get; set; }
/// <summary>
/// 日志名称
///</summary>
[SugarColumn(ColumnDescription = "日志名称", Length = 200)]
[DataTable(Order = 2, IsShow = true, Sortable = true, DefaultFilter = false)]
public string Name { get; set; }
/// <summary>
/// 操作人账号
///</summary>
[SugarColumn(ColumnDescription = "操作人账号", Length = 200, IsNullable = true)]
[DataTable(Order = 4, IsShow = true, Sortable = true, DefaultFilter = false)]
public string OpAccount { get; set; }
/// <summary>
/// 操作浏览器
///</summary>
[SugarColumn(ColumnDescription = "操作浏览器", Length = 200)]
[DataTable(Order = 5, IsShow = true, Sortable = true, DefaultFilter = false)]
public string OpBrowser { get; set; }
/// <summary>
/// 操作ip
///</summary>
[SugarColumn(ColumnDescription = "操作ip", Length = 200)]
[DataTable(Order = 6, IsShow = true, Sortable = true, DefaultFilter = false)]
public string OpIp { get; set; }
/// <summary>
/// 操作系统
///</summary>
[SugarColumn(ColumnDescription = "操作系统", Length = 200)]
[DataTable(Order = 7, IsShow = true, Sortable = true, DefaultFilter = false)]
public string OpOs { get; set; }
/// <summary>
/// 操作时间
///</summary>
[SugarColumn(ColumnDescription = "操作时间")]
[DataTable(Order = 8, IsShow = true, Sortable = true, DefaultFilter = false)]
public DateTime OpTime { get; set; }
/// <summary>
/// 验证Id
///</summary>
[SugarColumn(ColumnDescription = "验证Id")]
[IgnoreExcel]
[DataTable(Order = 9, IsShow = true, Sortable = true, DefaultFilter = true)]
public long VerificatId { get; set; }
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,11 +9,10 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
namespace ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 登录设备类型枚举
@@ -24,18 +22,10 @@ public enum AuthDeviceTypeEnum
/// <summary>
/// PC端
/// </summary>
[Description("PC")]
PC,
/// <summary>
/// 移动端
/// </summary>
[Description("APP")]
APP,
/// <summary>
/// Api
/// </summary>
[Description("Api")]
Api,
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,22 +9,23 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Components;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 键值表示
/// 字典表类型
/// </summary>
public class StringFilters
public enum DictTypeEnum
{
/// <summary>
///
/// 系统使用
/// </summary>
public string Key { get; set; }
System,
/// <summary>
///
/// 用户自定义
/// </summary>
public string Value { get; set; }
Define,
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,11 +9,15 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
public interface ISugarService
public enum LogCateGoryEnum
{
public SqlSugarClient NewContent { get; set; }
Login,
Logout,
Operate,
Exception
}

View File

@@ -0,0 +1,30 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
public enum RelationCategoryEnum
{
UserHasRole,
UserHasResource,
UserHasPermission,
UserHasOpenApiPermission,
UserHasModule,
UserDefaultRazor,
UserWorkbenchData,
RoleHasResource,
RoleHasPermission,
RoleHasOpenApiPermission,
RoleHasModule,
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,8 +9,14 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
global using Microsoft.Extensions.DependencyInjection;
global using NewLife.Caching;
namespace ThingsGateway.Admin.Application;
public enum ResourceCategoryEnum
{
Module,
Menu,
Button,
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,8 +9,13 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
global using System;
global using ThingsGateway.Components;
namespace ThingsGateway.Admin.Application;
public enum RoleCategoryEnum
{
Global,
Api,
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,8 +9,15 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
global using System;
global using ThingsGateway.Components;
namespace ThingsGateway.Admin.Application;
public enum TargetEnum
{
_self,
_blank,
_parent,
_top
}

View File

@@ -1,121 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 认证模块事件总线
/// </summary>
public class AuthEventSubscriber : IEventSubscriber, ISingleton
{
private readonly ISimpleCacheService _simpleCacheService;
public IServiceProvider _services { get; }
private readonly SqlSugarScope _db;
public AuthEventSubscriber(ISimpleCacheService simpleCacheService, IServiceProvider services)
{
_db = DbContext.Db;
_simpleCacheService = simpleCacheService;
_services = services;
}
/// <summary>
/// 登录事件
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
[EventSubscribe(EventSubscriberConst.Login)]
public async Task Login(EventHandlerExecutingContext context)
{
var loginEvent = (LoginEvent)context.Source.Payload;//获取参数
var sysUser = loginEvent.SysUser;
#region /
var key = SystemConst.Cache_LoginErrorCount + sysUser.Account;//获取登录错误次数Key值
_simpleCacheService.Remove(key);//移除登录错误次数
// 创建新的作用域
using var scope = _services.CreateScope();
// 解析服务
var configService = scope.ServiceProvider.GetRequiredService<IConfigService>();
var loginPolicy = await configService.GetListByCategoryAsync(CateGoryConst.Config_PWD_POLICY);//获取密码策略
//获取用户verificat列表
var tokenInfos = UserTokenCacheUtil.HashGetOne(sysUser.Id);
var userToken = tokenInfos.Where(it => it.Id == loginEvent.VerificatId).FirstOrDefault();
#endregion /
#region ,
sysUser.LastLoginDevice = sysUser.LatestLoginDevice;
sysUser.LastLoginIp = sysUser.LatestLoginIp;
sysUser.LastLoginTime = sysUser.LatestLoginTime;
sysUser.LatestLoginDevice = loginEvent.Device.ToString();
sysUser.LatestLoginIp = loginEvent.Ip;
sysUser.LatestLoginTime = loginEvent.DateTime;
#endregion ,
//更新用户登录信息
if (await _db.UpdateableWithAttr(sysUser).UpdateColumns(it => new
{
it.LastLoginDevice,
it.LastLoginIp,
it.LastLoginTime,
it.LatestLoginDevice,
it.LatestLoginIp,
it.LatestLoginTime,
}).ExecuteCommandAsync() > 0)
_simpleCacheService.HashAdd(SystemConst.Cache_SysUser, sysUser.Id.ToString(), sysUser);//更新Redis信息
await Task.CompletedTask;
}
/// <summary>
/// 登出事件
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
[EventSubscribe(EventSubscriberConst.LoginOut)]
public async Task LoginOut(EventHandlerExecutingContext context)
{
_ = (LoginEvent)context.Source.Payload;//获取参数
await Task.CompletedTask;
}
/// <summary>
/// 获取通知服务
/// </summary>
/// <returns></returns>
private INoticeService GetNoticeService()
{
var noticeService = _services.GetService<INoticeService>();//获取服务
return noticeService;
}
/// <summary>
/// 延迟执行
/// </summary>
/// <param name="millisecondsDelay">毫秒</param>
/// <param name="actionToExecute">方法</param>
private async Task DelayedExecutionAsync(int millisecondsDelay, Action actionToExecute)
{
// 延迟指定的时间
await Task.Delay(millisecondsDelay);
// 执行目标方法
actionToExecute.Invoke();
}
}

View File

@@ -1,90 +0,0 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 通知事件总线
/// </summary>
public class NoticeEventSubsciber : IEventSubscriber, ISingleton
{
private readonly ISimpleCacheService _simpleCacheService;
public IServiceProvider _service { get; }
private readonly SqlSugarScope _db;
public NoticeEventSubsciber(ISimpleCacheService simpleCacheService, IServiceProvider service)
{
_db = DbContext.Db;
_simpleCacheService = simpleCacheService;
_service = service;
}
/// <summary>
/// 通知用户下线事件
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
[EventSubscribe(EventSubscriberConst.UserLoginOut)]
public async Task UserLoginOut(EventHandlerExecutingContext context)
{
var loginEvent = (UserLoginOutEvent)context.Source.Payload;//获取参数
//客户端ID列表
var clientIds = new List<string>();
//遍历verificat列表获取客户端ID列表
loginEvent?.VerificatInfos?.ForEach(it =>
{
clientIds.AddRange(it.ClientIds);
});
await GetNoticeService().UserLoginOut(loginEvent.UserId, clientIds, loginEvent.Message);//发送消息
}
/// <summary>
/// 有新的消息
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
[EventSubscribe(EventSubscriberConst.NewMessage)]
public async Task NewMessage(EventHandlerExecutingContext context)
{
var newMessageEvent = (NewMessageEvent)context.Source.Payload;//获取参数
var clientIds = new List<string>();
//获取用户verificat列表
var verificatInfos = UserTokenCacheUtil.HashGet(newMessageEvent.UserIds.ToArray());
verificatInfos.ForEach(it =>
{
if (it != null)
{
it = it.Where(it => it.VerificatTimeout > DateTime.Now).ToList();//去掉登录超时的
//遍历verificat
it.ForEach(it =>
{
clientIds.AddRange(it.ClientIds);//添加到客户端ID列表
});
}
});
await GetNoticeService().NewMesage(newMessageEvent.UserIds, clientIds, newMessageEvent.Message);//发送消息
}
/// <summary>
/// 获取通知服务
/// </summary>
/// <returns></returns>
private INoticeService GetNoticeService()
{
var noticeService = _service.CreateScope().ServiceProvider.GetService<INoticeService>();//获取服务
return noticeService;
}
}

View File

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

View File

@@ -0,0 +1,33 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <inheritdoc/>
public class UserFriendlyException : Exception
{
public UserFriendlyException()
{
}
public UserFriendlyException(string? message) : base(message)
{
}
public UserFriendlyException(string? message, Exception? innerException) : base(message, innerException)
{
}
public bool? IsValidationException { get; set; }
}

View File

@@ -0,0 +1,78 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// ASP.NET Core 服务拓展类
/// </summary>
public static class AspNetCoreBuilderServiceCollectionExtensions
{
/// <summary>
/// 注册 Mvc 过滤器
/// </summary>
/// <typeparam name="TFilter"></typeparam>
/// <param name="mvcBuilder"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IMvcBuilder AddMvcFilter<TFilter>(this IMvcBuilder mvcBuilder, Action<MvcOptions> configure = default)
where TFilter : IFilterMetadata
{
mvcBuilder.Services.AddMvcFilter<TFilter>(configure);
return mvcBuilder;
}
/// <summary>
/// 注册 Mvc 过滤器
/// </summary>
/// <typeparam name="TFilter"></typeparam>
/// <param name="services"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IServiceCollection AddMvcFilter<TFilter>(this IServiceCollection services, Action<MvcOptions> configure = default)
where TFilter : IFilterMetadata
{
services.Configure<MvcOptions>(options =>
{
options.Filters.Add<TFilter>();
// 其他额外配置
configure?.Invoke(options);
});
return services;
}
/// <summary>
/// 注册 Mvc 过滤器
/// </summary>
/// <param name="services"></param>
/// <param name="filter"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IServiceCollection AddMvcFilter(this IServiceCollection services, IFilterMetadata filter, Action<MvcOptions> configure = default)
{
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(filter);
// 其他额外配置
configure?.Invoke(options);
});
return services;
}
}

View File

@@ -0,0 +1,43 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Microsoft.AspNetCore.Authorization;
/// <summary>
/// 授权处理上下文拓展类
/// </summary>
public static class AuthorizationHandlerContextExtensions
{
/// <summary>
/// 获取当前 HttpContext 上下文
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static DefaultHttpContext GetCurrentHttpContext(this AuthorizationHandlerContext context)
{
DefaultHttpContext? httpContext;
// 获取 httpContext 对象
if (context.Resource is AuthorizationFilterContext filterContext) httpContext = (DefaultHttpContext)filterContext.HttpContext;
else if (context.Resource is DefaultHttpContext defaultHttpContext) httpContext = defaultHttpContext;
else httpContext = null;
return httpContext;
}
}

View File

@@ -0,0 +1,191 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Mvc.Controllers;
using System.Text;
namespace Microsoft.AspNetCore.Http;
/// <summary>
/// Http 拓展类
/// </summary>
public static class HttpContextExtensions
{
/// <summary>
/// 获取 Action 特性
/// </summary>
/// <typeparam name="TAttribute"></typeparam>
/// <param name="httpContext"></param>
/// <returns></returns>
public static TAttribute GetMetadata<TAttribute>(this HttpContext httpContext)
where TAttribute : class
{
return httpContext.GetEndpoint()?.Metadata?.GetMetadata<TAttribute>();
}
/// <summary>
/// 获取 控制器/Action 描述器
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public static ControllerActionDescriptor GetControllerActionDescriptor(this HttpContext httpContext)
{
return httpContext.GetEndpoint()?.Metadata?.FirstOrDefault(u => u is ControllerActionDescriptor) as ControllerActionDescriptor;
}
/// <summary>
/// 设置规范化文档自动登录
/// </summary>
/// <param name="httpContext"></param>
/// <param name="accessToken"></param>
public static void SigninToSwagger(this HttpContext httpContext, string accessToken)
{
// 设置 Swagger 刷新自动授权
httpContext.Response.Headers["access-token"] = accessToken;
}
/// <summary>
/// 设置规范化文档退出登录
/// </summary>
/// <param name="httpContext"></param>
public static void SignoutToSwagger(this HttpContext httpContext)
{
httpContext.Response.Headers["access-token"] = "invalid_token";
}
/// <summary>
/// 设置响应头 Tokens
/// </summary>
/// <param name="httpContext"></param>
/// <param name="accessToken"></param>
/// <param name="refreshToken"></param>
public static void SetTokensOfResponseHeaders(this HttpContext httpContext, string accessToken, string? refreshToken = null)
{
httpContext.Response.Headers["access-token"] = accessToken;
if (!string.IsNullOrWhiteSpace(refreshToken))
{
httpContext.Response.Headers["x-access-token"] = refreshToken;
}
}
/// <summary>
/// 获取本机 IPv4地址
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetLocalIpAddressToIPv4(this HttpContext context)
{
return context.Connection.LocalIpAddress?.MapToIPv4()?.ToString();
}
/// <summary>
/// 获取本机 IPv6地址
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetLocalIpAddressToIPv6(this HttpContext context)
{
return context.Connection.LocalIpAddress?.MapToIPv6()?.ToString();
}
/// <summary>
/// 获取远程 IPv4地址
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetRemoteIpAddressToIPv4(this HttpContext context)
{
return context.Connection.RemoteIpAddress?.MapToIPv4()?.ToString();
}
/// <summary>
/// 获取远程 IPv6地址
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetRemoteIpAddressToIPv6(this HttpContext context)
{
return context.Connection.RemoteIpAddress?.MapToIPv6()?.ToString();
}
/// <summary>
/// 获取完整请求地址
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static string GetRequestUrlAddress(this HttpRequest request)
{
return new StringBuilder()
.Append(request.Scheme)
.Append("://")
.Append(request.Host)
.Append(request.PathBase)
.Append(request.Path)
.Append(request.QueryString)
.ToString();
}
/// <summary>
/// 获取来源地址
/// </summary>
/// <param name="request"></param>
/// <param name="refererHeaderKey"></param>
/// <returns></returns>
public static string GetRefererUrlAddress(this HttpRequest request, string refererHeaderKey = "Referer")
{
return request.Headers[refererHeaderKey].ToString();
}
/// <summary>
/// 读取 Body 内容
/// </summary>
/// <param name="httpContext"></param>
/// <remarks>需先在 Startup 的 Configure 中注册 app.EnableBuffering()</remarks>
/// <returns></returns>
public static async Task<string> ReadBodyContentAsync(this HttpContext httpContext)
{
if (httpContext == null) return default;
return await httpContext.Request.ReadBodyContentAsync();
}
/// <summary>
/// 读取 Body 内容
/// </summary>
/// <param name="request"></param>
/// <remarks>需先在 Startup 的 Configure 中注册 app.EnableBuffering()</remarks>
/// <returns></returns>
public static async Task<string> ReadBodyContentAsync(this HttpRequest request)
{
request.Body.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true);
var body = await reader.ReadToEndAsync();
request.Body.Seek(0, SeekOrigin.Begin);
return body;
}
/// <summary>
/// 判断是否是 WebSocket 请求
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static bool IsWebSocketRequest(this HttpContext context)
{
return context.WebSockets.IsWebSocketRequest || context.Request.Path == "/ws";
}
}

View File

@@ -0,0 +1,348 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.Extensions.Localization;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
namespace ThingsGateway.Admin.Application;
public static class LocalizerExtensions
{
public static PropertyInfo? GetPropertyByName(this Type type, string propertyName) => type.GetRuntimeProperties().FirstOrDefault(p => p.Name == propertyName);
public static MethodInfo? GetMethodByName(this Type type, string methodName) => type.GetRuntimeMethods().FirstOrDefault(p => p.Name == methodName);
public static FieldInfo? GetFieldByName(this Type type, string fieldName) => type.GetRuntimeFields().FirstOrDefault(p => p.Name == fieldName);
private static bool IsPublic(PropertyInfo p) => p.GetMethod != null && p.SetMethod != null && p.GetMethod.IsPublic && p.SetMethod.IsPublic;
/// <summary>
/// 验证整个模型时验证属性方法
/// </summary>
/// <param name="context"></param>
/// <param name="results"></param>
public static void ValidateProperty(this ValidationContext context, List<ValidationResult> results)
{
// 获得所有可写属性
var properties = context.ObjectType.GetRuntimeProperties().Where(p => IsPublic(p) && p.CanWrite && p.GetIndexParameters().Length == 0);
foreach (var pi in properties)
{
var fieldIdentifier = new FieldIdentifier(context.ObjectInstance, pi.Name);
context.DisplayName = fieldIdentifier.GetDisplayName();
context.MemberName = fieldIdentifier.FieldName;
var propertyValue = BootstrapBlazor.Components.Utility.GetPropertyValue(context.ObjectInstance, context.MemberName);
// 验证 DataAnnotations
var messages = new List<ValidationResult>();
// 组件进行验证
ValidateDataAnnotations(propertyValue, context, messages, pi);
if (messages.Count > 0)
results.AddRange(messages);
}
}
/// <summary>
/// 通过属性设置的 DataAnnotation 进行数据验证
/// </summary>
/// <param name="value"></param>
/// <param name="context"></param>
/// <param name="results"></param>
/// <param name="propertyInfo"></param>
/// <param name="memberName"></param>
private static void ValidateDataAnnotations(object? value, ValidationContext context, List<ValidationResult> results, PropertyInfo propertyInfo, string? memberName = null)
{
var rules = propertyInfo.GetCustomAttributes(true).OfType<ValidationAttribute>();
var metadataType = context.ObjectType.GetCustomAttribute<MetadataTypeAttribute>(false);
if (metadataType != null)
{
var p = metadataType.MetadataClassType.GetPropertyByName(propertyInfo.Name);
if (p != null)
{
rules = rules.Concat(p.GetCustomAttributes(true).OfType<ValidationAttribute>());
}
}
var displayName = context.DisplayName;
memberName ??= propertyInfo.Name;
var attributeSpan = nameof(Attribute).AsSpan();
foreach (var rule in rules)
{
var result = rule.GetValidationResult(value, context);
if (result != null && result != ValidationResult.Success)
{
var find = false;
var ruleNameSpan = rule.GetType().Name.AsSpan();
var index = ruleNameSpan.IndexOf(attributeSpan, StringComparison.OrdinalIgnoreCase);
var ruleName = ruleNameSpan[..index];
//// 通过设置 ErrorMessage 检索
//if (!context.ObjectType.Assembly.IsDynamic && !find
// && !string.IsNullOrEmpty(rule.ErrorMessage)
// && App.CreateLocalizerByType(context.ObjectType).TryGetLocalizerString(rule.ErrorMessage, out var msg))
//{
// rule.ErrorMessage = msg;
// find = true;
//}
//// 通过 Attribute 检索
//if (!rule.GetType().Assembly.IsDynamic && !find
// && App.CreateLocalizerByType(rule.GetType()).TryGetLocalizerString(nameof(rule.ErrorMessage), out msg))
//{
// rule.ErrorMessage = msg;
// find = true;
//}
// 通过 字段.规则名称 检索
if (!context.ObjectType.Assembly.IsDynamic && !find
&& App.CreateLocalizerByType(context.ObjectType).TryGetLocalizerString($"{memberName}.{ruleName.ToString()}", out var msg))
{
rule.ErrorMessage = msg;
find = true;
}
if (!find)
{
rule.ErrorMessage = result.ErrorMessage;
}
var errorMessage = !string.IsNullOrEmpty(rule.ErrorMessage) && rule.ErrorMessage.Contains("{0}")
? rule.FormatErrorMessage(displayName)
: rule.ErrorMessage;
results.Add(new ValidationResult(errorMessage, new string[] { memberName }));
}
}
}
/// <summary>
/// 获取指定 Type 的资源文件
/// </summary>
/// <param name="localizer"></param>
/// <param name="key"></param>
/// <param name="text"></param>
/// <returns></returns>
public static bool TryGetLocalizerString(this IStringLocalizer localizer, string key, [MaybeNullWhen(false)] out string? text)
{
var ret = false;
text = null;
var l = localizer[key];
if (l != null)
{
ret = !l.ResourceNotFound;
if (ret)
{
text = l.Value;
}
}
return ret;
}
/// <summary>
/// 获得类型自身的描述信息
/// </summary>
/// <param name="modelType"></param>
/// <returns></returns>
public static string GetTypeDisplayName(this Type modelType)
{
string fieldName = modelType.Name;
var cacheKey = $"{nameof(GetTypeDisplayName)}-{CultureInfo.CurrentUICulture.Name}-{modelType.FullName}-{modelType.TypeHandle.Value}";
var displayName = App.CacheService.GetOrCreate(cacheKey, entry =>
{
string? dn = null;
// 显示名称为空时通过资源文件查找 FieldName 项
var localizer = modelType.Assembly.IsDynamic ? null : App.CreateLocalizerByType(modelType);
var stringLocalizer = localizer?[fieldName];
if (stringLocalizer is { ResourceNotFound: false })
{
dn = stringLocalizer.Value;
}
else if (modelType.IsEnum)
{
var info = modelType.GetFieldByName(fieldName);
if (info != null)
{
dn = FindDisplayAttribute(info);
}
}
else if (TryGetProperty(modelType, fieldName, out var propertyInfo))
{
dn = FindDisplayAttribute(propertyInfo);
}
return dn;
}, 300);
return displayName ?? fieldName;
string? FindDisplayAttribute(MemberInfo memberInfo)
{
// 回退查找 Display 标签
var dn = memberInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name
?? memberInfo.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName
?? memberInfo.GetCustomAttribute<DescriptionAttribute>(true)?.Description;
return dn;
}
}
/// <summary>
/// 获取 DisplayName属性名称
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public static string Description<T>(this T item, Expression<Func<T, object>> accessor)
{
if (accessor.Body == null)
{
throw new ArgumentNullException(nameof(accessor));
}
var expression = accessor.Body;
if (expression is UnaryExpression unaryExpression && unaryExpression.NodeType == ExpressionType.Convert && unaryExpression.Type == typeof(object))
{
expression = unaryExpression.Operand;
}
if (expression is not MemberExpression memberExpression)
{
throw new ArgumentException("Can only access properties");
}
return typeof(T).GetPropertyDisplayName(memberExpression.Member.Name) ?? memberExpression.Member.Name;
}
/// <summary>
/// 获得类型属性的描述信息
/// </summary>
/// <param name="modelType"></param>
/// <param name="fieldName"></param>
/// <returns></returns>
public static string GetPropertyDisplayName(this Type modelType, string fieldName)
{
var cacheKey = $"{nameof(GetPropertyDisplayName)}-{CultureInfo.CurrentUICulture.Name}-{modelType.FullName}-{modelType.TypeHandle.Value}-{fieldName}";
var displayName = App.CacheService.GetOrCreate(cacheKey, entry =>
{
string? dn = null;
// 显示名称为空时通过资源文件查找 FieldName 项
var localizer = modelType.Assembly.IsDynamic ? null : App.CreateLocalizerByType(modelType);
var stringLocalizer = localizer?[fieldName];
if (stringLocalizer is { ResourceNotFound: false })
{
dn = stringLocalizer.Value;
}
else if (modelType.IsEnum)
{
var info = modelType.GetFieldByName(fieldName);
if (info != null)
{
dn = FindDisplayAttribute(info);
}
}
else if (TryGetProperty(modelType, fieldName, out var propertyInfo))
{
dn = FindDisplayAttribute(propertyInfo);
}
return dn;
}, 300);
return displayName ?? fieldName;
string? FindDisplayAttribute(MemberInfo memberInfo)
{
// 回退查找 Display 标签
var dn = memberInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name
?? memberInfo.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName
?? memberInfo.GetCustomAttribute<DescriptionAttribute>(true)?.Description;
return dn;
}
}
/// <summary>
/// 获得方法的描述信息
/// </summary>
/// <param name="modelType"></param>
/// <param name="methodName"></param>
/// <returns></returns>
public static string GetMethodDisplayName(this Type modelType, string methodName)
{
var cacheKey = $"{nameof(GetMethodDisplayName)}-{CultureInfo.CurrentUICulture.Name}-{modelType.FullName}-{modelType.TypeHandle.Value}-{methodName}";
var displayName = App.CacheService.GetOrCreate(cacheKey, entry =>
{
string? dn = null;
// 显示名称为空时通过资源文件查找 methodName 项
var localizer = modelType.Assembly.IsDynamic ? null : App.CreateLocalizerByType(modelType);
var stringLocalizer = localizer?[methodName];
if (stringLocalizer is { ResourceNotFound: false })
{
dn = stringLocalizer.Value;
}
else
{
var info = modelType.GetMethodByName(methodName);
if (info != null)
{
dn = FindDisplayAttribute(info);
}
}
return dn;
}, 300);
return displayName ?? methodName;
string? FindDisplayAttribute(MemberInfo memberInfo)
{
// 回退查找 Display 标签
var dn = memberInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name
?? memberInfo.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName
?? memberInfo.GetCustomAttribute<DescriptionAttribute>(true)?.Description;
return dn;
}
}
private static bool TryGetProperty(Type modelType, string fieldName, [NotNullWhen(true)] out PropertyInfo? propertyInfo)
{
var cacheKey = $"{nameof(TryGetProperty)}-{modelType.FullName}-{modelType.TypeHandle.Value}-{fieldName}";
propertyInfo = App.CacheService.GetOrCreate(cacheKey, entry =>
{
IEnumerable<PropertyInfo>? props;
// 支持 MetadataType
var metadataType = modelType.GetCustomAttribute<MetadataTypeAttribute>(false);
if (metadataType != null)
{
props = modelType.GetRuntimeProperties().AsEnumerable().Concat(metadataType.MetadataClassType.GetRuntimeProperties());
}
else
{
props = modelType.GetRuntimeProperties().AsEnumerable();
}
var pi = props.FirstOrDefault(p => p.Name == fieldName);
return pi;
}, 300);
return propertyInfo != null;
}
}

View File

@@ -1,4 +1,3 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
@@ -10,9 +9,12 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion.Extensions;
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Http;
using System.Collections.Concurrent;
using System.ComponentModel;
@@ -21,14 +23,157 @@ using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace ThingsGateway.Admin.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 对象拓展类
/// </summary>
[SuppressSniffer]
public static class ObjectExtension
public static class ObjectExtensions
{
/// <summary>
/// 将 DateTimeOffset 转换成本地 DateTime
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static DateTime ConvertToDateTime(this DateTimeOffset dateTime)
{
if (dateTime.Offset.Equals(TimeSpan.Zero))
return dateTime.UtcDateTime;
if (dateTime.Offset.Equals(TimeZoneInfo.Local.GetUtcOffset(dateTime.DateTime)))
return dateTime.ToLocalTime().DateTime;
else
return dateTime.DateTime;
}
/// <summary>
/// 将 DateTimeOffset? 转换成本地 DateTime?
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static DateTime? ConvertToDateTime(this DateTimeOffset? dateTime)
{
return dateTime.HasValue ? dateTime.Value.ConvertToDateTime() : null;
}
/// <summary>
/// 将 DateTime 转换成 DateTimeOffset
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static DateTimeOffset ConvertToDateTimeOffset(this DateTime dateTime)
{
return DateTime.SpecifyKind(dateTime, DateTimeKind.Local);
}
/// <summary>
/// 将 DateTime? 转换成 DateTimeOffset?
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static DateTimeOffset? ConvertToDateTimeOffset(this DateTime? dateTime)
{
return dateTime.HasValue ? dateTime.Value.ConvertToDateTimeOffset() : null;
}
/// <summary>
/// 将时间戳转换为 DateTime
/// </summary>
/// <param name="timestamp"></param>
/// <returns></returns>
internal static DateTime ConvertToDateTime(this long timestamp)
{
var timeStampDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var digitCount = (int)Math.Floor(Math.Log10(timestamp) + 1);
if (digitCount != 13 && digitCount != 10)
{
throw new ArgumentException("Data is not a valid timestamp format.");
}
return (digitCount == 13
? timeStampDateTime.AddMilliseconds(timestamp) // 13 位时间戳
: timeStampDateTime.AddSeconds(timestamp)).ToLocalTime(); // 10 位时间戳
}
/// <summary>
/// 将 IFormFile 转换成 byte[]
/// </summary>
/// <param name="formFile"></param>
/// <returns></returns>
public static byte[] ToByteArray(this IFormFile formFile)
{
var fileLength = formFile.Length;
using var stream = formFile.OpenReadStream();
var bytes = new byte[fileLength];
stream.Read(bytes, 0, (int)fileLength);
return bytes;
}
/// <summary>
/// 将流保存到本地磁盘
/// </summary>
/// <param name="stream"></param>
/// <param name="path"></param>
/// <returns></returns>
public static void CopyToSave(this Stream stream, string path)
{
// 空检查
if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path));
using var fileStream = File.Create(path);
stream.CopyTo(fileStream);
}
/// <summary>
/// 将字节数组保存到本地磁盘
/// </summary>
/// <param name="bytes"></param>
/// <param name="path"></param>
/// <returns></returns>
public static void CopyToSave(this byte[] bytes, string path)
{
using var stream = new MemoryStream(bytes);
stream.CopyToSave(path);
}
/// <summary>
/// 将流保存到本地磁盘
/// </summary>
/// <param name="stream"></param>
/// <param name="path">需包含文件名完整路径</param>
/// <returns></returns>
public static async Task CopyToSaveAsync(this Stream stream, string path)
{
// 空检查
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException(nameof(path));
}
// 文件名判断
if (string.IsNullOrWhiteSpace(Path.GetFileName(path)))
{
throw new ArgumentException("The parameter of <path> parameter must include the complete file name.");
}
using var fileStream = File.Create(path);
await stream.CopyToAsync(fileStream);
}
/// <summary>
/// 将字节数组保存到本地磁盘
/// </summary>
/// <param name="bytes"></param>
/// <param name="path"></param>
/// <returns></returns>
public static async Task CopyToSaveAsync(this byte[] bytes, string path)
{
using var stream = new MemoryStream(bytes);
await stream.CopyToSaveAsync(path);
}
/// <summary>
/// 判断是否是富基元类型
/// </summary>
@@ -109,32 +254,6 @@ public static class ObjectExtension
|| method.ReturnType.ToString().StartsWith(typeof(Task).FullName);
}
/// <summary>
/// 判断类型是否实现某个泛型
/// </summary>
/// <param name="type">类型</param>
/// <param name="generic">泛型类型</param>
/// <returns>bool</returns>
public static bool HasImplementedRawGeneric(this Type type, Type generic)
{
// 检查接口类型
var isTheRawGenericType = type.GetInterfaces().Any(IsTheRawGenericType);
if (isTheRawGenericType) return true;
// 检查类型
while (type != null && type != typeof(object))
{
isTheRawGenericType = IsTheRawGenericType(type);
if (isTheRawGenericType) return true;
type = type.BaseType;
}
return false;
// 判断逻辑
bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type);
}
/// <summary>
/// 判断是否是匿名类型
/// </summary>
@@ -407,6 +526,12 @@ public static class ObjectExtension
var _tempStr = !string.IsNullOrWhiteSpace(tempStr) ? tempStr : str;
tempStr = _tempStr[..^affix.Length];
endCleared = true;
if (string.IsNullOrWhiteSpace(tempStr))
{
tempStr = null;
endCleared = false;
}
}
if (startCleared && endCleared) break;
}

View File

@@ -0,0 +1,113 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using NewLife.Extension;
using SqlSugar;
namespace ThingsGateway.Admin.Application;
public static class QueryPageOptionsExtensions
{
/// <summary>
/// 根据查询条件返回sqlsugar ISugarQueryable
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="db"></param>
/// <param name="option"></param>
/// <returns></returns>
public static ISugarQueryable<T> GetQuery<T>(this SqlSugarClient db, QueryPageOptions option)
{
ISugarQueryable<T>? query = db.Queryable<T>();
var where = option.ToFilter();
if (where.HasFilters())
{
query = query.Where(where.GetFilterLambda<T>());//name asc模式
}
foreach (var item in option.SortList)
{
query = query.OrderByIF(!string.IsNullOrEmpty(item), $"{item}");//name asc模式
}
foreach (var item in option.AdvancedSortList)
{
query = query.OrderByIF(!string.IsNullOrEmpty(item), $"{item}");//name asc模式
}
query = query.OrderByIF(option.SortOrder != SortOrder.Unset, $"{option.SortName} {option.SortOrder}");
return query;
}
/// <summary>
/// 根据查询条件返回IEnumerable
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="datas"></param>
/// <param name="option"></param>
/// <returns></returns>
public static IEnumerable<T> GetData<T>(this IEnumerable<T> datas, QueryPageOptions option)
{
var where = option.ToFilter();
if (where.HasFilters())
{
datas = datas.Where(where.GetFilterFunc<T>());//name asc模式
}
if (option.SortList.Any())
{
datas = datas.Sort(option.SortList);//name asc模式
}
if (option.AdvancedSortList.Any())
{
datas = datas.Sort(option.AdvancedSortList);//name asc模式
}
if (option.SortOrder != SortOrder.Unset && !option.SortName.IsNullOrWhiteSpace())
{
datas = datas.Sort(option.SortName, option.SortOrder);
}
if (option.IsPage)
{
datas = datas.Skip((option.PageIndex - 1) * option.PageItems).Take(option.PageItems);
}
else if (option.IsVirtualScroll)
{
datas = datas.Skip((option.StartIndex) * option.PageItems).Take(option.PageItems);
}
return datas;
}
/// <summary>
/// 根据查询条件返回QueryData
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="datas"></param>
/// <param name="option"></param>
/// <returns></returns>
public static QueryData<T> GetQueryData<T>(this IEnumerable<T> datas, QueryPageOptions option)
{
var ret = new QueryData<T>()
{
IsSorted = option.SortOrder != SortOrder.Unset,
IsFiltered = option.Filters.Any(),
IsAdvanceSearch = option.AdvanceSearches.Any(),
IsSearch = option.Searches.Any() || option.CustomerSearches.Any()
};
var items = datas.GetData(option);
ret.TotalCount = datas.Count();
ret.Items = items;
return ret;
}
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,18 +9,31 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using SqlSugar;
using System.Linq.Expressions;
using System.Reflection;
namespace ThingsGateway.Admin.Core;
using ThingsGateway.Core;
/// <summary>
/// Sqlsugar分页拓展类
/// </summary>
public static class SqlSugarPageExtension
namespace ThingsGateway.Admin.Application;
public static class SqlSugarExtensions
{
public static async Task<bool> UpdateRangeAsync<T>(this SqlSugarClient db, List<T> updateObjs) where T : class, new()
{
return await db.Updateable(updateObjs).ExecuteCommandAsync() > 0;
}
public static async Task<bool> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new()
{
return await db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression)
.ExecuteCommandAsync() > 0;
}
public static ISugarQueryable<TEntity> ExportIgnoreColumns<TEntity>(this ISugarQueryable<TEntity> queryable)
{
return queryable.IgnoreColumns(
@@ -31,7 +43,6 @@ public static class SqlSugarPageExtension
nameof(BaseEntity.CreateTime),
nameof(BaseEntity.CreateUser),
nameof(BaseEntity.CreateUserId),
nameof(BaseEntity.SortCode),
nameof(BaseEntity.ExtJson),
nameof(BaseEntity.IsDelete),
nameof(BaseEntity.UpdateTime),
@@ -41,14 +52,6 @@ public static class SqlSugarPageExtension
);
}
/// <summary>
/// SqlSugar分页扩展
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="queryable"></param>
/// <param name="current"></param>
/// <param name="size"></param>
/// <returns></returns>
public static SqlSugarPagedList<TEntity> ToPagedList<TEntity>(this ISugarQueryable<TEntity> queryable, int current,
int size)
{
@@ -67,14 +70,6 @@ public static class SqlSugarPageExtension
};
}
/// <summary>
/// SqlSugar分页扩展
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="queryable"></param>
/// <param name="current"></param>
/// <param name="size"></param>
/// <returns></returns>
public static async Task<SqlSugarPagedList<TEntity>> ToPagedListAsync<TEntity>(this ISugarQueryable<TEntity> queryable,
int current, int size)
{
@@ -94,15 +89,50 @@ public static class SqlSugarPageExtension
}
/// <summary>
/// SqlSugar分页扩展
/// SqlSugar分页扩展,查询出结果后再转换实体类
/// </summary>
public static SqlSugarPagedList<TResult> ToPagedList<TEntity, TResult>(this ISugarQueryable<TEntity> queryable,
int current, int size)
{
var totalCount = 0;
var records = queryable.ToPageList(current, size, ref totalCount);
var totalPages = (int)Math.Ceiling(totalCount / (double)size);
return new SqlSugarPagedList<TResult>
{
Current = current,
Size = size,
Records = records.Cast<TResult>(),
Total = (int)totalCount,
Pages = totalPages,
HasNextPages = current < totalPages,
HasPrevPages = current - 1 > 0
};
}
/// <summary>
/// SqlSugar分页扩展,查询出结果后再转换实体类
/// </summary>
public static async Task<SqlSugarPagedList<TResult>> ToPagedListAsync<TEntity, TResult>(this ISugarQueryable<TEntity> queryable,
int current, int size)
{
RefAsync<int> totalCount = 0;
var records = await queryable.ToPageListAsync(current, size, totalCount);
var totalPages = (int)Math.Ceiling(totalCount / (double)size);
return new SqlSugarPagedList<TResult>
{
Current = current,
Size = size,
Records = records.Cast<TResult>(),
Total = (int)totalCount,
Pages = totalPages,
HasNextPages = current < totalPages,
HasPrevPages = current - 1 > 0
};
}
/// <summary>
/// SqlSugar分页扩展查询前扩展转换实体类
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="queryable"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="expression"></param>
/// <returns></returns>
public static SqlSugarPagedList<TResult> ToPagedList<TEntity, TResult>(this ISugarQueryable<TEntity> queryable, int pageIndex,
int pageSize, Expression<Func<TEntity, TResult>> expression)
{
@@ -122,15 +152,9 @@ public static class SqlSugarPageExtension
}
/// <summary>
/// SqlSugar分页扩展
/// SqlSugar分页扩展,查询前扩展转换实体类
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="queryable"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="expression"></param>
/// <returns></returns>
public static async Task<SqlSugarPagedList<TResult>> ToPagedListAsync<TEntity, TResult>(
this ISugarQueryable<TEntity> queryable, int pageIndex, int pageSize, Expression<Func<TEntity, TResult>> expression)
{

View File

@@ -0,0 +1,177 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Collections.Concurrent;
using System.Reflection;
using ThingsGateway.Core;
namespace ThingsGateway.Admin.Application;
public static class StartupExtensions
{
private static ConcurrentBag<AppStartup> AppStartups = new();
/// <summary>
/// 反射获取所有AppStartup的继承类执行名称为第一个参数是<see cref="IServiceCollection"/>的方法
/// </summary>
/// <param name="service"></param>
public static void ConfigureServices(this WebApplicationBuilder service)
{
AddStartups(service);
}
/// <summary>
/// ConfigureServices获取的全部实例中执行名称为第一个参数是<see cref="IApplicationBuilder"/>的方法
/// </summary>
public static void UseServices(this IApplicationBuilder builder)
{
UseStartups(AppStartups, builder);
}
/// <summary>
/// 添加 Startup 自动扫描
/// </summary>
internal static void AddStartups(this WebApplicationBuilder builder)
{
App.Configuration = builder.Configuration;
if (builder.Environment is IWebHostEnvironment webHostEnvironment)
App.WebRootPath = webHostEnvironment.WebRootPath;
App.ContentRootPath = builder.Environment.ContentRootPath;
App.IsDevelopment = builder.Environment.IsDevelopment();
// 扫描所有继承 AppStartup 的类
var startups = App.EffectiveTypes
.Where(u => typeof(AppStartup).IsAssignableFrom(u) && u.IsClass && !u.IsAbstract && !u.IsGenericType)
.OrderByDescending(u => GetStartupOrder(u));
// 注册自定义 startup
foreach (var type in startups)
{
var startup = Activator.CreateInstance(type) as AppStartup;
AppStartups.Add(startup!);
// 获取所有符合依赖注入格式的方法如返回值void且第一个参数是 IServiceCollection 类型
var serviceMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(u => u.ReturnType == typeof(void)
&& u.GetParameters().Length > 0
&& u.GetParameters().First().ParameterType == typeof(IServiceCollection));
if (!serviceMethods.Any()) continue;
// 自动安装属性调用
foreach (var method in serviceMethods)
{
method.Invoke(startup, new[] { builder.Services });
}
}
}
/// <summary>
/// 批量将自定义 AppStartup 添加到 Startup.cs 的 Configure 中
/// </summary>
/// <param name="startups"></param>
/// <param name="app"></param>
private static void UseStartups(IEnumerable<AppStartup> startups, IApplicationBuilder app)
{
App.RootServices = app.ApplicationServices;
App.CacheService = app.ApplicationServices.GetRequiredService<ICacheService>();
// 遍历所有
foreach (var startup in startups)
{
var type = startup.GetType();
// 获取所有符合依赖注入格式的方法,如返回值 void且第一个参数是 IApplicationBuilder 类型
var configureMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(u => u.ReturnType == typeof(void)
&& u.GetParameters().Length > 0
&& u.GetParameters().First().ParameterType == typeof(IApplicationBuilder));
if (!configureMethods.Any()) continue;
// 自动安装属性调用
foreach (var method in configureMethods)
{
method.Invoke(startup, ResolveMethodParameterInstances(app, method));
}
}
AppStartups.Clear();
}
/// <summary>
/// 获取 Startup 排序
/// </summary>
/// <param name="type">排序类型</param>
/// <returns>int</returns>
private static int GetStartupOrder(Type type)
{
return !type.IsDefined(typeof(AppStartupAttribute), true) ? 0 : type.GetCustomAttribute<AppStartupAttribute>(true)!.Order;
}
/// <summary>
/// 解析方法参数实例
/// </summary>
/// <param name="app"></param>
/// <param name="method"></param>
/// <returns></returns>
private static object[] ResolveMethodParameterInstances(IApplicationBuilder app, MethodInfo method)
{
// 获取方法所有参数
var parameters = method.GetParameters();
var parameterInstances = new object[parameters.Length];
parameterInstances[0] = app;
// 解析服务
for (var i = 1; i < parameters.Length; i++)
{
var parameter = parameters[i];
parameterInstances[i] = app.ApplicationServices.GetRequiredService(parameter.ParameterType);
}
return parameterInstances;
}
}
public abstract class AppStartup
{
}
/// <summary>
/// 注册服务启动配置
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class AppStartupAttribute : Attribute
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="order"></param>
public AppStartupAttribute(int order)
{
Order = order;
}
/// <summary>
/// 排序
/// </summary>
public int Order { get; set; }
}

View File

@@ -1,5 +1,4 @@
#region copyright

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -10,14 +9,15 @@
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Core.Extension;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// TextWriter扩展
/// </summary>
public static class TextWriterExtension
public static class TextWriterExtensions
{
private const string DefaultBackgroundColor = "\x1B[49m";
private const string DefaultForegroundColor = "\x1B[39m\x1B[22m";

View File

@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Rougamo />
</Weavers>

View File

@@ -0,0 +1,133 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
#if !NET5_0
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// DateOnly 类型序列化
/// </summary>
public class NewtonsoftJsonDateOnlyJsonConverter : JsonConverter<DateOnly>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonDateOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonDateOnlyJsonConverter(string format = "yyyy-MM-dd")
{
Format = format;
}
/// <summary>
/// 日期格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override DateOnly ReadJson(JsonReader reader, Type objectType, DateOnly existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var value = JValue.ReadFrom(reader).Value<string>();
return DateOnly.Parse(value);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, DateOnly value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToString(Format));
}
}
/// <summary>
/// DateOnly? 类型序列化
/// </summary>
public class NewtonsoftJsonNullableDateOnlyJsonConverter : JsonConverter<DateOnly?>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonNullableDateOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonNullableDateOnlyJsonConverter(string format = "yyyy-MM-dd")
{
Format = format;
}
/// <summary>
/// 日期格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override DateOnly? ReadJson(JsonReader reader, Type objectType, DateOnly? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var value = JValue.ReadFrom(reader).Value<string>();
return !string.IsNullOrWhiteSpace(value) ? DateOnly.Parse(value) : null;
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, DateOnly? value, JsonSerializer serializer)
{
if (value == null) writer.WriteNull();
else serializer.Serialize(writer, value.Value.ToString(Format));
}
}
#endif

View File

@@ -0,0 +1,130 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Newtonsoft.Json;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// DateTime 类型序列化
/// </summary>
public class NewtonsoftJsonDateTimeJsonConverter : JsonConverter<DateTime>
{
/// <summary>
/// 默认构造函数
/// </summary>
public NewtonsoftJsonDateTimeJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonDateTimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return Penetrates.ConvertToDateTime(ref reader);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
/// <exception cref="NotImplementedException"></exception>
public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToString(Format));
}
}
/// <summary>
/// DateTime 类型序列化
/// </summary>
public class NewtonsoftNullableJsonDateTimeJsonConverter : JsonConverter<DateTime?>
{
/// <summary>
/// 默认构造函数
/// </summary>
public NewtonsoftNullableJsonDateTimeJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftNullableJsonDateTimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override DateTime? ReadJson(JsonReader reader, Type objectType, DateTime? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return Penetrates.ConvertToDateTime(ref reader);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
/// <exception cref="NotImplementedException"></exception>
public override void WriteJson(JsonWriter writer, DateTime? value, JsonSerializer serializer)
{
if (value == null) writer.WriteNull();
else serializer.Serialize(writer, value.Value.ToString(Format));
}
}

View File

@@ -0,0 +1,171 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Newtonsoft.Json;
using ThingsGateway.Core.Extension;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// DateTimeOffset 类型序列化
/// </summary>
public class NewtonsoftJsonDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonDateTimeOffsetJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
/// <param name="outputToLocalDateTime"></param>
public NewtonsoftJsonDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss", bool outputToLocalDateTime = false)
: this(format)
{
Localized = outputToLocalDateTime;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 是否输出为为当地时间
/// </summary>
public bool Localized { get; private set; } = false;
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override DateTimeOffset ReadJson(JsonReader reader, Type objectType, DateTimeOffset existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return DateTime.SpecifyKind(Penetrates.ConvertToDateTime(ref reader), Localized ? DateTimeKind.Local : DateTimeKind.Utc);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
/// <exception cref="NotImplementedException"></exception>
public override void WriteJson(JsonWriter writer, DateTimeOffset value, JsonSerializer serializer)
{
// 判断是否序列化成当地时间
var formatDateTime = Localized ? value.ConvertToDateTime() : value;
serializer.Serialize(writer, formatDateTime.ToString(Format));
}
}
/// <summary>
/// DateTimeOffset 类型序列化
/// </summary>
public class NewtonsoftJsonNullableDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset?>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonNullableDateTimeOffsetJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonNullableDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
/// <param name="outputToLocalDateTime"></param>
public NewtonsoftJsonNullableDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss", bool outputToLocalDateTime = false)
: this(format)
{
Localized = outputToLocalDateTime;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 是否输出为为当地时间
/// </summary>
public bool Localized { get; private set; } = false;
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override DateTimeOffset? ReadJson(JsonReader reader, Type objectType, DateTimeOffset? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return DateTime.SpecifyKind(Penetrates.ConvertToDateTime(ref reader), Localized ? DateTimeKind.Local : DateTimeKind.Utc);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
/// <exception cref="NotImplementedException"></exception>
public override void WriteJson(JsonWriter writer, DateTimeOffset? value, JsonSerializer serializer)
{
if (value == null) writer.WriteNull();
else
{
// 判断是否序列化成当地时间
var formatDateTime = Localized ? value.ConvertToDateTime() : value;
serializer.Serialize(writer, formatDateTime!.Value.ToString(Format));
}
}
}

View File

@@ -0,0 +1,144 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// 解决 long 精度问题
/// </summary>
public class NewtonsoftJsonLongToStringJsonConverter : JsonConverter<long>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonLongToStringJsonConverter()
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="overMaxLengthOf17"></param>
public NewtonsoftJsonLongToStringJsonConverter(bool overMaxLengthOf17 = false)
{
OverMaxLengthOf17 = overMaxLengthOf17;
}
/// <summary>
/// 是否超过最大长度 17 再处理
/// </summary>
public bool OverMaxLengthOf17 { get; set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override long ReadJson(JsonReader reader, Type objectType, long existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jt = JValue.ReadFrom(reader);
return jt.Type == JTokenType.Null // 处理 public long? Property { get; set;} = 0; 情况,也就是类型是 long? 但是也给了默认值
? existingValue
: jt.Value<long>();
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, long value, JsonSerializer serializer)
{
if (OverMaxLengthOf17)
{
if (value.ToString().Length <= 17) writer.WriteValue(value);
else writer.WriteValue(value.ToString());
}
else writer.WriteValue(value.ToString());
}
}
/// <summary>
/// 解决 long? 精度问题
/// </summary>
public class NewtonsoftJsonNullableLongToStringJsonConverter : JsonConverter<long?>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonNullableLongToStringJsonConverter()
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="overMaxLengthOf17"></param>
public NewtonsoftJsonNullableLongToStringJsonConverter(bool overMaxLengthOf17 = false)
{
OverMaxLengthOf17 = overMaxLengthOf17;
}
/// <summary>
/// 是否超过最大长度 17 再处理
/// </summary>
public bool OverMaxLengthOf17 { get; set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override long? ReadJson(JsonReader reader, Type objectType, long? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jt = JValue.ReadFrom(reader);
return jt.Value<long?>();
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, long? value, JsonSerializer serializer)
{
if (value == null) writer.WriteNull();
else
{
var newValue = value.Value;
if (OverMaxLengthOf17)
{
if (newValue.ToString().Length <= 17) writer.WriteValue(newValue);
else writer.WriteValue(newValue.ToString());
}
else writer.WriteValue(newValue.ToString());
}
}
}

View File

@@ -0,0 +1,133 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
#if !NET5_0
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// TimeOnly 类型序列化
/// </summary>
public class NewtonsoftJsonTimeOnlyJsonConverter : JsonConverter<TimeOnly>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonTimeOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonTimeOnlyJsonConverter(string format = "HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override TimeOnly ReadJson(JsonReader reader, Type objectType, TimeOnly existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var value = JValue.ReadFrom(reader).Value<string>();
return TimeOnly.Parse(value!);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, TimeOnly value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToString(Format));
}
}
/// <summary>
/// TimeOnly? 类型序列化
/// </summary>
public class NewtonsoftJsonNullableTimeOnlyJsonConverter : JsonConverter<TimeOnly?>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonNullableTimeOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonNullableTimeOnlyJsonConverter(string format = "HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override TimeOnly? ReadJson(JsonReader reader, Type objectType, TimeOnly? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var value = JValue.ReadFrom(reader).Value<string>();
return !string.IsNullOrWhiteSpace(value) ? TimeOnly.Parse(value) : null;
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, TimeOnly? value, JsonSerializer serializer)
{
if (value == null) writer.WriteNull();
else serializer.Serialize(writer, value.Value.ToString(Format));
}
}
#endif

View File

@@ -0,0 +1,123 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// DateOnly 类型序列化
/// </summary>
public class SystemTextJsonDateOnlyJsonConverter : JsonConverter<DateOnly>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonDateOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonDateOnlyJsonConverter(string format = "yyyy-MM-dd")
{
Format = format;
}
/// <summary>
/// 日期格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateOnly.Parse(reader.GetString()!);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(Format));
}
}
/// <summary>
/// DateOnly? 类型序列化
/// </summary>
public class SystemTextJsonNullableDateOnlyJsonConverter : JsonConverter<DateOnly?>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonNullableDateOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonNullableDateOnlyJsonConverter(string format = "yyyy-MM-dd")
{
Format = format;
}
/// <summary>
/// 日期格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override DateOnly? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateOnly.TryParse(reader.GetString(), out var date) ? date : null;
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, DateOnly? value, JsonSerializerOptions options)
{
if (value == null) writer.WriteNullValue();
else writer.WriteStringValue(value.Value.ToString(Format));
}
}

View File

@@ -0,0 +1,123 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// DateTime 类型序列化
/// </summary>
public class SystemTextJsonDateTimeJsonConverter : JsonConverter<DateTime>
{
/// <summary>
/// 默认构造函数
/// </summary>
public SystemTextJsonDateTimeJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonDateTimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return Penetrates.ConvertToDateTime(ref reader);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(Format));
}
}
/// <summary>
/// DateTime? 类型序列化
/// </summary>
public class SystemTextJsonNullableDateTimeJsonConverter : JsonConverter<DateTime?>
{
/// <summary>
/// 默认构造函数
/// </summary>
public SystemTextJsonNullableDateTimeJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonNullableDateTimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return Penetrates.ConvertToDateTime(ref reader);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
{
if (value == null) writer.WriteNullValue();
else writer.WriteStringValue(value.Value.ToString(Format));
}
}

View File

@@ -0,0 +1,164 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using System.Text.Json;
using System.Text.Json.Serialization;
using ThingsGateway.Core.Extension;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// DateTimeOffset 类型序列化
/// </summary>
public class SystemTextJsonDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonDateTimeOffsetJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
/// <param name="outputToLocalDateTime"></param>
public SystemTextJsonDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss", bool outputToLocalDateTime = false)
: this(format)
{
Localized = outputToLocalDateTime;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 是否输出为为当地时间
/// </summary>
public bool Localized { get; private set; } = false;
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.SpecifyKind(Penetrates.ConvertToDateTime(ref reader), Localized ? DateTimeKind.Local : DateTimeKind.Utc);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
{
// 判断是否序列化成当地时间
var formatDateTime = Localized ? value.ConvertToDateTime() : value;
writer.WriteStringValue(formatDateTime.ToString(Format));
}
}
/// <summary>
/// DateTimeOffset? 类型序列化
/// </summary>
public class SystemTextJsonNullableDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset?>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonNullableDateTimeOffsetJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonNullableDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
/// <param name="outputToLocalDateTime"></param>
public SystemTextJsonNullableDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss", bool outputToLocalDateTime = false)
: this(format)
{
Localized = outputToLocalDateTime;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 是否输出为为当地时间
/// </summary>
public bool Localized { get; private set; } = false;
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override DateTimeOffset? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.SpecifyKind(Penetrates.ConvertToDateTime(ref reader), Localized ? DateTimeKind.Local : DateTimeKind.Utc);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, DateTimeOffset? value, JsonSerializerOptions options)
{
if (value == null) writer.WriteNullValue();
else
{
// 判断是否序列化成当地时间
var formatDateTime = Localized ? value.ConvertToDateTime() : value;
writer.WriteStringValue(formatDateTime!.Value.ToString(Format));
}
}
}

View File

@@ -0,0 +1,139 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// 解决 long 精度问题
/// </summary>
public class SystemTextJsonLongToStringJsonConverter : JsonConverter<long>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonLongToStringJsonConverter()
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="overMaxLengthOf17"></param>
public SystemTextJsonLongToStringJsonConverter(bool overMaxLengthOf17 = false)
{
OverMaxLengthOf17 = overMaxLengthOf17;
}
/// <summary>
/// 是否超过最大长度 17 再处理
/// </summary>
public bool OverMaxLengthOf17 { get; set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.TokenType == JsonTokenType.String
? long.Parse(reader.GetString()!)
: reader.GetInt64();
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
{
if (OverMaxLengthOf17)
{
if (value.ToString().Length <= 17) writer.WriteNumberValue(value);
else writer.WriteStringValue(value.ToString());
}
else writer.WriteStringValue(value.ToString());
}
}
/// <summary>
/// 解决 long? 精度问题
/// </summary>
public class SystemTextJsonNullableLongToStringJsonConverter : JsonConverter<long?>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonNullableLongToStringJsonConverter()
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="overMaxLengthOf17"></param>
public SystemTextJsonNullableLongToStringJsonConverter(bool overMaxLengthOf17 = false)
{
OverMaxLengthOf17 = overMaxLengthOf17;
}
/// <summary>
/// 是否超过最大长度 17 再处理
/// </summary>
public bool OverMaxLengthOf17 { get; set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.TokenType == JsonTokenType.String
? long.Parse(reader.GetString()!)
: reader.GetInt64();
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options)
{
if (value == null) writer.WriteNullValue();
else
{
var newValue = value.Value;
if (OverMaxLengthOf17)
{
if (newValue.ToString().Length <= 17) writer.WriteNumberValue(newValue);
else writer.WriteStringValue(newValue.ToString());
}
else writer.WriteStringValue(newValue.ToString());
}
}
}

View File

@@ -0,0 +1,127 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
#if !NET5_0
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// TimeOnly 类型序列化
/// </summary>
public class SystemTextJsonTimeOnlyJsonConverter : JsonConverter<TimeOnly>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonTimeOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonTimeOnlyJsonConverter(string format = "HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return TimeOnly.Parse(reader.GetString()!);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(Format));
}
}
/// <summary>
/// TimeOnly? 类型序列化
/// </summary>
public class SystemTextJsonNullableTimeOnlyJsonConverter : JsonConverter<TimeOnly?>
{
/// <summary>
/// 默认构造函数
/// </summary>
public SystemTextJsonNullableTimeOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonNullableTimeOnlyJsonConverter(string format = "HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override TimeOnly? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return TimeOnly.TryParse(reader.GetString(), out var time) ? time : null;
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, TimeOnly? value, JsonSerializerOptions options)
{
if (value == null) writer.WriteNullValue();
else writer.WriteStringValue(value.Value.ToString(Format));
}
}
#endif

View File

@@ -0,0 +1,58 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Mvc;
using ThingsGateway.JsonSerialization;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Json 序列化服务拓展类
/// </summary>
public static class JsonSerializationServiceCollectionExtensions
{
/// <summary>
/// 配置 Json 序列化提供器
/// </summary>
/// <typeparam name="TJsonSerializerProvider"></typeparam>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddJsonSerialization<TJsonSerializerProvider>(this IServiceCollection services)
where TJsonSerializerProvider : class, IJsonSerializerProvider
{
services.AddSingleton<IJsonSerializerProvider, TJsonSerializerProvider>();
return services;
}
/// <summary>
/// 配置 JsonOptions 序列化选项
/// <para>主要给非 Web 环境使用</para>
/// </summary>
/// <param name="services"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IServiceCollection AddJsonOptions(this IServiceCollection services, Action<JsonOptions> configure)
{
// 手动添加配置
services.Configure<JsonOptions>(options =>
{
configure?.Invoke(options);
});
return services;
}
}

View File

@@ -0,0 +1,85 @@

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using ThingsGateway.JsonSerialization;
namespace Newtonsoft.Json;
/// <summary>
/// Newtonsoft.Json 拓展
/// </summary>
public static class NewtonsoftJsonExtensions
{
/// <summary>
/// 添加 DateTime/DateTime?/DateTimeOffset/DateTimeOffset? 类型序列化处理
/// </summary>
/// <param name="converters"></param>
/// <param name="outputFormat"></param>
/// <param name="localized">自动转换 DateTimeOffset 为当地时间</param>
/// <returns></returns>
public static IList<JsonConverter> AddDateTimeTypeConverters(this IList<JsonConverter> converters, string outputFormat = "yyyy-MM-dd HH:mm:ss", bool localized = false)
{
converters.Add(new NewtonsoftJsonDateTimeJsonConverter(outputFormat));
converters.Add(new NewtonsoftNullableJsonDateTimeJsonConverter(outputFormat));
converters.Add(new NewtonsoftJsonDateTimeOffsetJsonConverter(outputFormat, localized));
converters.Add(new NewtonsoftJsonNullableDateTimeOffsetJsonConverter(outputFormat, localized));
return converters;
}
/// <summary>
/// 添加 long/long? 类型序列化处理
/// </summary>
/// <param name="converters"></param>
/// <param name="overMaxLengthOf17">是否超过最大长度 17 再处理</param>
/// <remarks></remarks>
public static IList<JsonConverter> AddLongTypeConverters(this IList<JsonConverter> converters, bool overMaxLengthOf17 = false)
{
converters.Add(new NewtonsoftJsonLongToStringJsonConverter(overMaxLengthOf17));
converters.Add(new NewtonsoftJsonNullableLongToStringJsonConverter(overMaxLengthOf17));
return converters;
}
/// <summary>
/// 添加 DateOnly/DateOnly? 类型序列化处理
/// </summary>
/// <param name="converters"></param>
/// <returns></returns>
public static IList<JsonConverter> AddDateOnlyConverters(this IList<JsonConverter> converters)
{
#if !NET5_0
converters.Add(new NewtonsoftJsonDateOnlyJsonConverter());
converters.Add(new NewtonsoftJsonNullableDateOnlyJsonConverter());
#endif
return converters;
}
/// <summary>
/// 添加 TimeOnly/TimeOnly? 类型序列化处理
/// </summary>
/// <param name="converters"></param>
/// <returns></returns>
public static IList<JsonConverter> AddTimeOnlyConverters(this IList<JsonConverter> converters)
{
#if !NET5_0
converters.Add(new NewtonsoftJsonTimeOnlyJsonConverter());
converters.Add(new NewtonsoftJsonNullableTimeOnlyJsonConverter());
#endif
return converters;
}
}

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