Compare commits

...

44 Commits

Author SHA1 Message Date
Diego
98f3f2d519 添加 `过滤离线变量` 插件属性 2025-05-14 13:01:12 +08:00
Diego
b76b4e8d68 变量表索引删除语句兼容性增强 2025-05-13 19:27:50 +08:00
Diego
07285a7c61 mqtt增加qos属性 2025-05-13 19:27:27 +08:00
Diego
03c0dfef37 增加业务设备日志 2025-05-12 15:26:51 +08:00
Diego
6ef6929c35 优化api权限树 2025-05-12 10:21:41 +08:00
Diego
e3c0c173f0 更新依赖 2025-05-12 08:53:25 +08:00
Diego
7d64e058d4 更新依赖 2025-05-08 16:34:48 +08:00
Diego
e97ee9b64b 10.5.15 2025-05-07 22:08:54 +08:00
Diego
6a03e39eeb 10.5.14 2025-05-07 22:00:31 +08:00
Diego
525ec740b5 添加脚本demo 2025-05-06 11:43:56 +08:00
2248356998 qq.com
b790cf5f4e 更新依赖 2025-05-05 20:25:43 +08:00
Diego
d1248811fd build: 10.5.11 2025-04-30 23:05:36 +08:00
2248356998 qq.com
022d016e8e feat: 添加采集组 2025-04-30 23:04:51 +08:00
Diego
f73245e650 build: 10.5.10 2025-04-30 15:31:39 +08:00
2248356998 qq.com
484461fa05 更新依赖 2025-04-30 15:29:19 +08:00
Diego
7e0b7aff2a feat: sqldb支持数组 2025-04-28 15:52:32 +08:00
Diego
6c450dcb09 feat: sqldb支持数组类型 2025-04-28 15:52:11 +08:00
Diego
227f44283f build: 10.5.8 2025-04-28 15:30:10 +08:00
Diego
74f6e79625 build: 10.5.7
优化opcua变量缓存
修复cron表达式间隔
2025-04-27 16:35:58 +08:00
Diego
cec43e2ce8 feat: 防呆设计,强制设置通道的最大并发数 2025-04-27 10:13:35 +08:00
Diego
7553b258bb build: 10.5.5 2025-04-26 17:46:31 +08:00
Diego
8bdbdc117e 支持bind链路设置多个通道 2025-04-26 17:10:46 +08:00
Diego
0e206be296 更新依赖 2025-04-26 16:17:02 +08:00
Diego
00b7353433 兼容性增强 2025-04-26 15:53:20 +08:00
Diego
44e7a83593 更新依赖 2025-04-26 15:02:57 +08:00
Diego
dd68d555d4 feat: 支持字节数组上传 2025-04-24 10:25:36 +08:00
2248356998 qq.com
0456296103 更新依赖包 2025-04-23 23:07:27 +08:00
Diego
a1b66277ff nuget 2025-04-23 15:28:53 +08:00
Diego
50758b79bc 更新依赖包 2025-04-23 11:48:45 +08:00
Diego
06a1f902ad 修改Startup方法名称 2025-04-23 10:56:03 +08:00
Diego
58f8b23b7c build:10.5.1
fix: 反向代理正确获取客户端IP

refactor: 添加demo站点域名
2025-04-22 11:45:14 +08:00
Diego
9e7c348b15 添加大数据写入方法 2025-04-21 23:05:10 +08:00
Diego
5f5ff8b43b 删除实体部分特性 2025-04-21 17:53:33 +08:00
Diego
f626b4e5fc sql插件增加分表模式 属性 2025-04-21 11:00:05 +08:00
2248356998 qq.com
bc23200e66 更新依赖 2025-04-20 20:32:41 +08:00
Diego
95ab59fd5a build: 10.4.21 2025-04-18 11:27:48 +08:00
Diego
0bbee003b0 app.json 2025-04-18 09:21:31 +08:00
Diego
d20cb7a928 兼容10.4.8以下数据库 2025-04-17 09:41:15 +08:00
Diego
952c134aba build: 10.4.19 2025-04-16 09:08:35 +08:00
2248356998 qq.com
5533d980e3 删除不必要的代码 2025-04-15 21:14:35 +08:00
2248356998 qq.com
aa0213818f 添加规则引擎日志清理任务 2025-04-15 20:59:01 +08:00
2248356998 qq.com
af88ffd57c 添加语言资源 2025-04-15 20:07:44 +08:00
2248356998 qq.com
87447b1c2a 传输model添加runtimetype 2025-04-15 20:01:21 +08:00
Diego
384a12880b 网关表格按钮权限 2025-04-15 10:13:37 +08:00
239 changed files with 4825 additions and 4289 deletions

View File

@@ -20,11 +20,14 @@ A cross-platform, high-performance edge data collection gateway based on net9.
## Demo

[Demo](http://47.119.161.158:5000/)
[Demo](https://demo.thingsgateway.cn/)

Account: **SuperAdmin**

Password: **111111**

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

View File

@@ -13,7 +13,7 @@
## 演示
[ThingsGateway演示地址](http://47.119.161.158:5000/)
[ThingsGateway演示地址](https://demo.thingsgateway.cn/)
账户 : **SuperAdmin**

View File

@@ -137,7 +137,7 @@ public sealed class OperDescAttribute : MoAttribute
Name = (localizerType == null ? App.CreateLocalizerByType(typeof(OperDescAttribute)) : App.CreateLocalizerByType(localizerType))![Description],
Category = LogCateGoryEnum.Operate,
ExeStatus = true,
OpIp = AppService?.RemoteIpAddress?.MapToIPv4()?.ToString() ?? string.Empty,
OpIp = AppService?.RemoteIpAddress ?? string.Empty,
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
OpTime = DateTime.Now,

View File

@@ -26,6 +26,7 @@ namespace ThingsGateway.Admin.Application;
[Route("openapi/auth")]
[Authorize(AuthenticationSchemes = "Bearer")]
[LoggingMonitor]
[ApiController]
public class OpenApiController : ControllerBase
{
private readonly IAuthService _authService;

View File

@@ -15,16 +15,13 @@ namespace ThingsGateway.Admin.Application;
[Route("api/[controller]/[action]")]
[AllowAnonymous]
[ApiController]
public class TestController : ControllerBase
{
[HttpPost]
public Task Test(string data)
[HttpGet]
public void Test()
{
for (int i = 0; i < 3; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
return Task.CompletedTask;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}

View File

@@ -23,7 +23,7 @@ public class SysDict : BaseEntity
/// <summary>
/// 类型
///</summary>
[SugarColumn(ColumnDescription = "类型", Length = 200)]
[SugarColumn(ColumnDescription = "类型")]
[AutoGenerateColumn(Ignore = true, Filterable = true, Sortable = true)]
public virtual DictTypeEnum DictType { get; set; }

View File

@@ -24,7 +24,7 @@ public class SysOperateLog
/// <summary>
/// 日志分类
///</summary>
[SugarColumn(ColumnDescription = "日志分类", Length = 200)]
[SugarColumn(ColumnDescription = "日志分类")]
[AutoGenerateColumn(Order = 1, Filterable = true, Sortable = true)]
public LogCateGoryEnum Category { get; set; }

View File

@@ -54,7 +54,7 @@ public class SysPosition : BaseEntity
/// <summary>
/// 分类
///</summary>
[SugarColumn(ColumnName = "Category", ColumnDescription = "分类", Length = 200)]
[SugarColumn(ColumnName = "Category", ColumnDescription = "分类")]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual PositionCategoryEnum Category { get; set; }

View File

@@ -22,7 +22,7 @@ public class SysRelation : PrimaryKeyEntity
/// <summary>
/// 分类
///</summary>
[SugarColumn(ColumnDescription = "分类", Length = 200)]
[SugarColumn(ColumnDescription = "分类")]
public RelationCategoryEnum Category { get; set; }
/// <summary>

View File

@@ -41,7 +41,7 @@ public class SysRole : BaseEntity
/// <summary>
/// 分类
///</summary>
[SugarColumn(ColumnDescription = "分类", Length = 200, IsNullable = false)]
[SugarColumn(ColumnDescription = "分类", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual RoleCategoryEnum Category { get; set; }

View File

@@ -1,4 +1,15 @@
{
"ThingsGateway.Admin.Application.BaseDataEntity": {
"CreateOrgId": "CreateOrgId"
},
"ThingsGateway.Admin.Application.BaseEntity": {
"SortCode": "SortCode",
"CreateTime": "CreateTime",
"CreateUser": "CreateUser",
"UpdateTime": "UpdateTime",
"UpdateUser": "UpdateUser"
},
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
"UserExpire": "User expired, please login again"
},
@@ -24,9 +35,6 @@
"LatestLoginTime": "LatestLoginTime",
"LatestLoginDevice": "LatestLoginDevice",
"LatestLoginAddress": "LatestLoginAddress",
"SortCode": "SortCode",
"CreateTime": "CreateTime",
"UpdateTime": "UpdateTime",
"OrgNames": "OrgNames",
"PositionName": "PositionName",
"OrgId": "Org",
@@ -60,9 +68,6 @@
"Name": "Name",
"Name.Required": "{0} is required",
"Category": "Category",
"SortCode": "Sort",
"CreateTime": "CreateTime",
"UpdateTime": "UpdateTime",
"OrgId": "Org",
"Global": "Global",
"Status": "Status",
@@ -105,9 +110,6 @@
"Category": "Category",
"Target": "Target",
"NavLinkMatch": "NavLinkMatch",
"SortCode": "Sort",
"CreateTime": "CreateTime",
"UpdateTime": "UpdateTime",
"ParentId": "Parent",
"ResourceDup": "Duplicate name {0} exists",
"ResourceParentChoiceSelf": "Parent cannot choose itself",
@@ -134,9 +136,6 @@
"Status": "Status",
"OrgId": "Organization",
"Remark": "Remarks",
"SortCode": "SortCode",
"CreateTime": "CreateTime",
"UpdateTime": "UpdateTime",
"Dup": "Duplicate position exists with Category {0} and Name {1}",
"CodeDup": "Duplicate code {0} exists",
"NameDup": "Duplicate name {0} exists",
@@ -159,9 +158,6 @@
"Names": "Names",
"Remark": "Remarks",
"DirectorId": "Director",
"SortCode": "SortCode",
"CreateTime": "CreateTime",
"UpdateTime": "UpdateTime",
"Dup": "Duplicate organization exists with Category {0} and Name {1}",
"CodeDup": "Duplicate code {0} exists",
"NameDup": "Duplicate name {0} exists",
@@ -358,9 +354,6 @@
"Name": "Name",
"Code": "Code",
"Remark": "Remark",
"SortCode": "Sort",
"CreateTime": "CreateTime",
"UpdateTime": "UpdateTime",
"DemoCanotUpdateWebsitePolicy": "DEMO environment does not allow modifying website settings",
"DictDup": "Duplicate configuration exists, category {0}, name {1}"
},

View File

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

View File

@@ -22,26 +22,19 @@ using System.Globalization;
using System.Reflection;
using ThingsGateway.Extension;
using ThingsGateway.SpecificationDocument;
namespace ThingsGateway.Admin.Application;
internal sealed class ApiPermissionService : IApiPermissionService
{
private readonly IApiDescriptionGroupCollectionProvider _apiDescriptionGroupCollectionProvider;
private readonly SwaggerGeneratorOptions _generatorOptions;
public ApiPermissionService(
IOptions<SwaggerGeneratorOptions> generatorOptions,
IApiDescriptionGroupCollectionProvider apiDescriptionGroupCollectionProvider)
{
_generatorOptions = generatorOptions.Value;
_apiDescriptionGroupCollectionProvider = apiDescriptionGroupCollectionProvider;
}
private IEnumerable<string> GetDocumentNames()
{
return _generatorOptions.SwaggerDocs.Keys;
}
/// <inheritdoc />
public List<OpenApiPermissionTreeSelector> ApiPermissionTreeSelector()
@@ -53,37 +46,37 @@ internal sealed class ApiPermissionService : IApiPermissionService
permissions = new();
Dictionary<string, OpenApiPermissionTreeSelector> groupOpenApis = new();
foreach (var item in GetDocumentNames())
{
OpenApiPermissionTreeSelector openApiPermissionTreeSelector = new() { ApiName = item ?? "Default" };
groupOpenApis.TryAdd(openApiPermissionTreeSelector.ApiName, openApiPermissionTreeSelector);
}
var apiDescriptions = _apiDescriptionGroupCollectionProvider.ApiDescriptionGroups.Items;
foreach (var item1 in apiDescriptions)
{
foreach (var item in item1.Items)
{
if (item.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
OpenApiPermissionTreeSelector openApiPermissionTreeSelector = new() { ApiName = controllerActionDescriptor.ControllerName ?? "Default" };
groupOpenApis.TryAdd(openApiPermissionTreeSelector.ApiName, openApiPermissionTreeSelector);
}
}
}
// 获取所有需要数据权限的控制器
var controllerTypes =
App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(RolePermissionAttribute), false));
foreach (var groupOpenApi in groupOpenApis)
//foreach (var groupOpenApi in groupOpenApis)
{
foreach (var apiDescriptionGroup in apiDescriptions)
{
var routes = apiDescriptionGroup.Items.Where(api => api.ActionDescriptor is ControllerActionDescriptor);
OpenApiPermissionTreeSelector openApiPermissionTreeSelector = groupOpenApi.Value;
Dictionary<string, OpenApiPermissionTreeSelector> openApiPermissionTreeSelectorDict = new();
foreach (var route in routes)
{
if (!SpecificationDocumentBuilder.CheckApiDescriptionInCurrentGroup(groupOpenApi.Key, route))
{
continue;
}
var actionDesc = (ControllerActionDescriptor)route.ActionDescriptor;
if (!actionDesc.ControllerTypeInfo.CustomAttributes.Any(a => a.AttributeType == typeof(RolePermissionAttribute)))
continue;
@@ -116,10 +109,8 @@ internal sealed class ApiPermissionService : IApiPermissionService
}
openApiPermissionTreeSelector.Children.AddRange(openApiPermissionTreeSelectorDict.Values);
if (openApiPermissionTreeSelector.Children.Any(a => a.Children.Count > 0))
permissions.Add(openApiPermissionTreeSelector);
if (openApiPermissionTreeSelectorDict.Values.Any(a => a.Children.Count > 0))
permissions.AddRange(openApiPermissionTreeSelectorDict.Values);
}

View File

@@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using System.Net;
using System.Security.Claims;
using UAParser;
@@ -72,7 +71,7 @@ public class AppService : IAppService
}
public ClaimsPrincipal? User => App.User;
public IPAddress? RemoteIpAddress => App.HttpContext?.Connection?.RemoteIpAddress;
public string? RemoteIpAddress => App.HttpContext?.GetRemoteIpAddressToIPv4();
public int LocalPort => App.HttpContext.Connection.LocalPort;
}

View File

@@ -11,7 +11,6 @@
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using System.Net;
using System.Security.Claims;
using UAParser;
@@ -24,7 +23,7 @@ public class HybridAppService : IAppService
{
var str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0";
ClientInfo = Parser.GetDefault().Parse(str);
RemoteIpAddress = IPAddress.Parse("127.0.0.1");
RemoteIpAddress = "127.0.0.1";
}
public ClientInfo? ClientInfo { get; }
@@ -56,7 +55,7 @@ public class HybridAppService : IAppService
}
}
public IPAddress? RemoteIpAddress { get; }
public string? RemoteIpAddress { get; }
public string GetReturnUrl(string returnUrl)
{

View File

@@ -9,7 +9,6 @@
//------------------------------------------------------------------------------
using System.Net;
using System.Security.Claims;
using UAParser;
@@ -31,7 +30,7 @@ public interface IAppService
/// <summary>
/// RemoteIpAddress
/// </summary>
public IPAddress? RemoteIpAddress { get; }
public string? RemoteIpAddress { get; }
/// <summary>
/// GetReturnUrl

View File

@@ -96,16 +96,16 @@ public class AuthService : IAuthService
/// </summary>
public async Task LoginOutAsync()
{
if (UserManager.UserId == 0)
if (UserManager.VerificatId == 0)
return;
var verificatId = UserManager.UserId;
var verificatId = UserManager.VerificatId;
//获取用户信息
var userinfo = await _sysUserService.GetUserByAccountAsync(UserManager.UserAccount, UserManager.TenantId).ConfigureAwait(false);
if (userinfo != null)
{
var loginEvent = new LoginEvent
{
Ip = _appService.RemoteIpAddress?.MapToIPv4()?.ToString(),
Ip = _appService.RemoteIpAddress,
SysUser = userinfo,
VerificatId = verificatId
};
@@ -236,7 +236,7 @@ public class AuthService : IAuthService
//登录事件参数
var logingEvent = new LoginEvent
{
Ip = _appService.RemoteIpAddress?.MapToIPv4()?.ToString(),
Ip = _appService.RemoteIpAddress,
Device = App.GetService<IAppService>().ClientInfo?.OS?.ToString(),
Expire = expire,
SysUser = sysUser,

View File

@@ -47,12 +47,10 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new(
}
/// <inheritdoc/>
public Task<bool> DeleteAsync(IEnumerable<T> models)
public async Task<bool> DeleteAsync(IEnumerable<T> models)
{
if (models.FirstOrDefault() is IPrimaryIdEntity)
return DeleteAsync(models.Select(a => ((IPrimaryIdEntity)a).Id));
else
return Task.FromResult(false);
using var db = GetDB();
return await db.Deleteable<T>().In(models.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false);
}
/// <inheritdoc/>
@@ -165,4 +163,6 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new(
{
return DbContext.Db.GetConnectionScopeWithAttr<T>().CopyNew();
}
}

View File

@@ -103,4 +103,46 @@ public static class DbContext
Console.WriteLine("【Sql执行时间】" + DateTime.Now.ToDefaultDateTimeFormat());
Console.WriteLine("【Sql语句】" + msg + Environment.NewLine);
}
public static async Task BulkCopyAsync<TITEM>(this SqlSugarClient db, List<TITEM> datas, int size) where TITEM : class, new()
{
switch (db.CurrentConnectionConfig.DbType)
{
case DbType.MySql:
case DbType.SqlServer:
case DbType.Sqlite:
case DbType.Oracle:
case DbType.PostgreSQL:
case DbType.Dm:
case DbType.MySqlConnector:
case DbType.Kdbndp:
await db.Fastest<TITEM>().PageSize(size).BulkCopyAsync(datas).ConfigureAwait(false);
break;
default:
await db.Insertable(datas).PageSize(size).ExecuteCommandAsync().ConfigureAwait(false);
break;
}
}
public static async Task BulkUpdateAsync<TITEM>(this SqlSugarClient db, List<TITEM> datas, int size) where TITEM : class, new()
{
switch (db.CurrentConnectionConfig.DbType)
{
case DbType.MySql:
case DbType.SqlServer:
case DbType.Sqlite:
case DbType.Oracle:
case DbType.PostgreSQL:
case DbType.Dm:
case DbType.MySqlConnector:
case DbType.Kdbndp:
await db.Fastest<TITEM>().PageSize(size).BulkUpdateAsync(datas).ConfigureAwait(false);
break;
default:
await db.Updateable(datas).PageSize(size).ExecuteCommandAsync().ConfigureAwait(false);
break;
}
}
}

View File

@@ -10,6 +10,7 @@
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using SqlSugar;
@@ -23,7 +24,7 @@ namespace ThingsGateway.Admin.Application;
[AppStartup(1000000000)]
public class Startup : AppStartup
{
public void ConfigureAdminApp(IServiceCollection services)
public void Configure(IServiceCollection services)
{
Directory.CreateDirectory("DB");
@@ -75,7 +76,7 @@ public class Startup : AppStartup
}
public void UseAdminCore(IServiceProvider serviceProvider)
public void Use(IApplicationBuilder applicationBuilder)
{
//检查ConfigId
var configIdGroup = DbContext.DbConfigs.GroupBy(it => it.ConfigId);

View File

@@ -18,11 +18,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.2" />
<!--<PackageReference Include="MiniExcel" Version="1.39.0" />-->
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.4" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.188" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.193" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
@@ -31,9 +30,9 @@
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.4" />
<PackageReference Include="System.Formats.Asn1" Version="9.0.4" />
<PackageReference Include="System.Threading.RateLimiting" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.5" />
<PackageReference Include="System.Formats.Asn1" Version="9.0.5" />
<PackageReference Include="System.Threading.RateLimiting" Version="9.0.5" />
</ItemGroup>
<ItemGroup>
<Content Remove="SeedData\Admin\*.json" />

View File

@@ -41,7 +41,7 @@ public partial class SessionPage
{
var op = new DialogOption()
{
IsScrolling = false,
IsScrolling = true,
Title = Localizer[nameof(VerificatInfo)],
ShowMaximizeButton = true,
Class = "dialog-table",

View File

@@ -15,7 +15,7 @@ namespace ThingsGateway.Admin.Razor;
[AppStartup(-1)]
public class Startup : AppStartup
{
public void ConfigureAdminApp(IServiceCollection services)
public void Configure(IServiceCollection services)
{
services.AddScoped<IMenuService, MenuService>();
services.AddBootstrapBlazorTableExportService();

View File

@@ -9,10 +9,10 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net8.0'">
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.14" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.16" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net9.0'">
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.5" />
</ItemGroup>
<PropertyGroup>

View File

@@ -10,6 +10,9 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.StaticFiles;
@@ -18,6 +21,7 @@ using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Unicode;
@@ -291,6 +295,21 @@ public class Startup : AppStartup
services.AddAuthorizationCore();
services.AddScoped<IAuthorizationHandler, BlazorServerAuthenticationHandler>();
services.AddScoped<AuthenticationStateProvider, BlazorServerAuthenticationStateProvider>();
#if NET9_0_OR_GREATER
var certificate = X509CertificateLoader.LoadPkcs12FromFile("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet);
#else
var certificate = new X509Certificate2("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet);
#endif
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo("../keys"))
.ProtectKeysWithCertificate(certificate)
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
}
@@ -298,9 +317,9 @@ public class Startup : AppStartup
public void Use(IApplicationBuilder applicationBuilder, IWebHostEnvironment env)
{
var app = (WebApplication)applicationBuilder;
app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All, KnownNetworks = { }, KnownProxies = { } });
app.UseBootstrapBlazor();
app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All });
// 启用本地化
var option = app.Services.GetService<IOptions<RequestLocalizationOptions>>();

View File

@@ -45,7 +45,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.4" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.5" />
</ItemGroup>
<!--安装服务守护-->
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
@@ -54,8 +54,8 @@
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.5" />
</ItemGroup>
<ItemGroup>
@@ -72,6 +72,9 @@
<None Update="pm2-linux.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="ThingsGateway.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="thingsgateway.service">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

Binary file not shown.

View File

@@ -106,7 +106,7 @@ public static class HttpContextExtensions
/// <param name="context"></param>
/// <param name="xff">是否优先取 X-Forwarded-For</param>
/// <returns></returns>
public static string GetRemoteIpAddressToIPv4(this HttpContext context, bool xff = false)
public static string GetRemoteIpAddressToIPv4(this HttpContext context, bool xff = true)
{
var ipv4 = context.Connection.RemoteIpAddress?.MapToIPv4()?.ToString();

View File

@@ -147,7 +147,7 @@ internal sealed class ScheduleHostedService : BackgroundService
await BackgroundProcessing(stoppingToken).ConfigureAwait(false);
}
_logger.LogCritical($"Schedule hosted service is stopped.");
_logger.LogInformation($"Schedule hosted service is stopped.");
}
/// <summary>

View File

@@ -43,18 +43,18 @@
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.14" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.16" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.16" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="8.0.1" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="9.0.4" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="9.0.4" />
<PackageReference Include="System.Text.Json" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="9.0.5" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="9.0.5" />
<PackageReference Include="System.Text.Json" Version="9.0.5" />
</ItemGroup>

View File

@@ -32,7 +32,7 @@ public class TimeTick
/// <summary>
/// 上次触发时间
/// </summary>
public DateTime LastTime { get; private set; } = DateTime.Now;
public DateTime LastTime { get; private set; } = DateTime.UtcNow;
/// <summary>
/// 是否触发时间刻度
@@ -62,7 +62,7 @@ public class TimeTick
return result;
}
public DateTime GetNextTime(DateTime currentTime, bool setLastTime = true)
public DateTime GetNextTime(DateTime currentTime, bool setLastTime = false)
{
// 在没有 Cron 表达式的情况下,使用固定间隔
if (cron == null)
@@ -86,7 +86,7 @@ public class TimeTick
}
public DateTime GetNextTime(bool setLastTime = true) => GetNextTime(DateTime.UtcNow, setLastTime);
public DateTime GetNextTime(bool setLastTime = false) => GetNextTime(DateTime.UtcNow, setLastTime);
/// <summary>
/// 是否到达设置的时间间隔

View File

@@ -13,10 +13,10 @@
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.AspNetCore.Components.WebView" Version="8.0.14" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView" Version="8.0.16" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.AspNetCore.Components.WebView" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView" Version="9.0.5" />
</ItemGroup>
</Project>

View File

@@ -22,12 +22,34 @@ public static class JSRuntimeExtensions
/// 获取文化信息
/// </summary>
/// <param name="jsRuntime"></param>
public static ValueTask<string> GetCulture(this IJSRuntime jsRuntime) => jsRuntime.InvokeAsync<string>("getCultureLocalStorage");
public static async ValueTask<string> GetCulture(this IJSRuntime jsRuntime)
{
try
{
return await jsRuntime.InvokeAsync<string>("getCultureLocalStorage");
}
catch
{
return null;
}
}
/// <summary>
/// 设置文化信息
/// </summary>
/// <param name="jsRuntime"></param>
/// <param name="cultureName"></param>
public static ValueTask SetCulture(this IJSRuntime jsRuntime, string cultureName) => jsRuntime.InvokeVoidAsync("setCultureLocalStorage", cultureName);
public static async ValueTask SetCulture(this IJSRuntime jsRuntime, string cultureName)
{
try
{
await jsRuntime.InvokeVoidAsync("setCultureLocalStorage", cultureName);
}
catch
{
}
}
}

View File

@@ -8,6 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using ThingsGateway.NewLife;
@@ -48,7 +49,7 @@ public class Startup : AppStartup
}
/// <inheritdoc/>
public void UseService(IServiceProvider serviceProvider)
public void Use(IApplicationBuilder applicationBuilder)
{
}
}

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor" Version="9.5.5" />
<PackageReference Include="BootstrapBlazor" Version="9.6.2-beta05" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>

View File

@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<PluginVersion>10.4.18</PluginVersion>
<ProPluginVersion>10.4.18</ProPluginVersion>
<PluginVersion>10.5.19</PluginVersion>
<ProPluginVersion>10.5.19</ProPluginVersion>
<AuthenticationVersion>2.1.7</AuthenticationVersion>
</PropertyGroup>

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CS-Script" Version="4.8.27" />
<PackageReference Include="CS-Script" Version="4.9.6" />
</ItemGroup>
<ItemGroup>

View File

@@ -15,8 +15,9 @@ namespace ThingsGateway.Debug;
[AppStartup(100000000)]
public class Startup : AppStartup
{
public void ConfigureAdminApp(IServiceCollection services)
public void Configure(IServiceCollection services)
{
Foundation.LocalizerUtil.SetLocalizerFactory((a) => App.CreateLocalizerByType(a));
services.AddScoped<IPlatformService, PlatformService>();
}
}

View File

@@ -94,11 +94,11 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
return WaitLocks.GetOrAdd(key, (a) => new WaitLock(WaitLock.MaxCount));
}
public override Task StopAsync()
public override Task<Result> StopAsync(CancellationToken token)
{
WaitLocks.ForEach(a => a.Value.SafeDispose());
WaitLocks.Clear();
return base.StopAsync();
return base.StopAsync(token);
}
private ConcurrentDictionary<EndPoint, WaitLock> _waitLocks = new();

View File

@@ -128,17 +128,17 @@ public class OtherChannel : SetupConfigObject, IClientChannel
public Protocol Protocol => new Protocol("Other");
public DateTime LastReceivedTime { get; private set; }
public DateTimeOffset LastReceivedTime { get; private set; }
public DateTime LastSentTime { get; private set; }
public DateTimeOffset LastSentTime { get; private set; }
public bool IsClient => true;
public bool Online => true;
public Task CloseAsync(string msg)
public Task<Result> CloseAsync(string msg, CancellationToken token)
{
return Task.CompletedTask;
return Task.FromResult(Result.Success);
}
public Task ConnectAsync(int millisecondsTimeout, CancellationToken token)

View File

@@ -80,7 +80,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
{
if (HeartbeatByte.SequenceEqual(e.ByteBlock.AsSegment(0, len)))
{
if (DateTime.UtcNow - socket.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200))
{
await Task.Delay(200).ConfigureAwait(false);
}

View File

@@ -42,7 +42,10 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
set
{
_heartbeat = value;
HeartbeatByte = new ArraySegment<byte>(Encoding.UTF8.GetBytes(value));
if (!_heartbeat.IsNullOrEmpty())
{
HeartbeatByte = new ArraySegment<byte>(Encoding.UTF8.GetBytes(value));
}
}
}
private string _heartbeat;
@@ -59,6 +62,8 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
return;//此处可判断,如果为服务器,则不用使用心跳。
}
HeartbeatTime = Math.Max(HeartbeatTime, 1000);
if (DtuId.IsNullOrWhiteSpace()) return;
if (client is ITcpClient tcpClient)
@@ -81,7 +86,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
try
{
if (DateTime.UtcNow - tcpClient.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
if (DateTimeOffset.Now - tcpClient.LastSentTime < TimeSpan.FromMilliseconds(200))
{
await Task.Delay(200).ConfigureAwait(false);
}

View File

@@ -70,13 +70,13 @@ public static class PluginUtil
{
action += a =>
{
a.UseCheckClear()
a.UseTcpSessionCheckClear()
.SetCheckClearType(CheckClearType.All)
.SetTick(TimeSpan.FromMilliseconds(channelOptions.CheckClearTime))
.SetOnClose(async (c, t) =>
{
await c.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
c.SafeClose($"{channelOptions.CheckClearTime}ms Timeout");
await c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout").ConfigureAwait(false);
});
};

View File

@@ -74,8 +74,9 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
//private readonly WaitLock _connectLock = new WaitLock();
/// <inheritdoc/>
public override async Task CloseAsync(string msg)
public override async Task<Result> CloseAsync(string msg, CancellationToken token)
{
if (Online)
{
try
@@ -83,11 +84,12 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
//await _connectLock.WaitAsync().ConfigureAwait(false);
if (Online)
{
await base.CloseAsync(msg).ConfigureAwait(false);
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
if (!Online)
{
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
}
return result;
}
}
finally
@@ -95,6 +97,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
//_connectLock.Release();
}
}
return Result.Success;
}
/// <inheritdoc/>

View File

@@ -72,7 +72,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
//private readonly WaitLock _connectLock = new WaitLock();
/// <inheritdoc/>
public override async Task CloseAsync(string msg)
public override async Task<Result> CloseAsync(string msg, CancellationToken token)
{
if (Online)
{
@@ -81,11 +81,12 @@ public class TcpClientChannel : TcpClient, IClientChannel
//await _connectLock.WaitAsync().ConfigureAwait(false);
if (Online)
{
await base.CloseAsync(msg).ConfigureAwait(false);
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
if (!Online)
{
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
}
return result;
}
}
finally
@@ -93,6 +94,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
//_connectLock.Release();
}
}
return Result.Success;
}
/// <inheritdoc/>

View File

@@ -94,22 +94,22 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
}
/// <inheritdoc/>
public override async Task StopAsync()
public override async Task<Result> StopAsync(CancellationToken token)
{
if (Monitors.Any())
{
try
{
await _connectLock.WaitAsync().ConfigureAwait(false);
await _connectLock.WaitAsync(token).ConfigureAwait(false);
if (Monitors.Any())
{
await ClearAsync().ConfigureAwait(false);
var iPHost = Monitors.FirstOrDefault()?.Option.IpHost;
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
if (!Monitors.Any())
Logger?.Info($"{iPHost}{DefaultResource.Localizer["ServiceStoped"]}");
return result;
}
}
finally
@@ -120,8 +120,10 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
}
else
{
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
return result;
}
return Result.Success; ;
}
@@ -192,9 +194,9 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
public ChannelEventHandler Stoping { get; set; } = new();
/// <inheritdoc/>
public Task CloseAsync(string msg)
public Task<Result> CloseAsync(string msg, CancellationToken token)
{
return StopAsync();
return StopAsync(token);
}
/// <inheritdoc/>

View File

@@ -64,10 +64,10 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>
public override Task CloseAsync(string msg)
public override Task<Result> CloseAsync(string msg, CancellationToken token)
{
WaitHandlePool.SafeDispose();
return base.CloseAsync(msg);
return base.CloseAsync(msg, token);
}
/// <inheritdoc/>

View File

@@ -73,9 +73,9 @@ public class UdpSessionChannel : UdpSession, IClientChannel
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
/// <inheritdoc/>
public Task CloseAsync(string msg)
public Task<Result> CloseAsync(string msg, CancellationToken token)
{
return StopAsync();
return StopAsync(token);
}
/// <inheritdoc/>
@@ -127,26 +127,28 @@ public class UdpSessionChannel : UdpSession, IClientChannel
}
/// <inheritdoc/>
public override async Task StopAsync()
public override async Task<Result> StopAsync(CancellationToken token)
{
if (Monitor != null)
{
try
{
await _connectLock.WaitAsync().ConfigureAwait(false);
await _connectLock.WaitAsync(token).ConfigureAwait(false);
if (Monitor != null)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
if (Monitor == null)
{
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
Logger?.Info($"{DefaultResource.Localizer["ServiceStoped"]}");
}
return result;
}
else
{
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
return result;
}
}
finally
@@ -156,7 +158,8 @@ public class UdpSessionChannel : UdpSession, IClientChannel
}
else
{
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
return result;
}
}

View File

@@ -46,9 +46,9 @@ public abstract class DeviceBase : DisposableObject, IDevice
return;
if (channel.Collects.Count > 0)
{
var device = channel.Collects.First();
if (device.GetType() != GetType())
throw new InvalidOperationException("The channel already exists in the device of another type");
//var device = channel.Collects.First();
//if (device.GetType() != GetType())
// throw new InvalidOperationException("The channel already exists in the device of another type");
if (!SupportMultipleDevice())
throw new InvalidOperationException("The proactive response device does not support multiple devices");
@@ -97,22 +97,24 @@ public abstract class DeviceBase : DisposableObject, IDevice
Channel.Stoping.Add(ChannelStoping);
Channel.Started.Add(ChannelStarted);
Channel.ChannelReceived.Add(ChannelReceived);
SetChannel();
}
}
protected virtual void SetChannel()
{
Channel.ChannelOptions.MaxConcurrentCount = 1;
}
/// <inheritdoc/>
~DeviceBase()
{
this.SafeDispose();
}
#region
/// <inheritdoc/>
public virtual int SendDelayTime { get; set; }
/// <inheritdoc/>
public virtual int Timeout { get; set; } = 3000;
#region
private ILog? _deviceLogger;
@@ -135,6 +137,18 @@ public abstract class DeviceBase : DisposableObject, IDevice
public bool OnLine => Channel.Online;
#endregion
#region
/// <inheritdoc/>
public virtual int SendDelayTime { get; set; }
/// <inheritdoc/>
public virtual int Timeout { get; set; } = 3000;
/// <summary>
/// <inheritdoc cref="IThingsGatewayBitConverter.IsStringReverseByteWord"/>
/// </summary>
@@ -221,6 +235,14 @@ public abstract class DeviceBase : DisposableObject, IDevice
}
else
{
if (Channel?.Collects?.Count > 1)
{
var dataHandlingAdapter = GetDataAdapter();
if (adapter.GetType() != dataHandlingAdapter.GetType())
{
clientChannel.SetDataHandlingAdapter(dataHandlingAdapter);
}
}
}
}
@@ -546,7 +568,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
finally
{
waitLock.Release();
clientChannel.WaitHandlePool.Destroy(waitData);
clientChannel.WaitHandlePool.Destroy(sign);
Channel.ChannelReceivedWaitDict.TryRemove(sign, out _);
}
}
@@ -944,7 +966,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
if (tcpServiceChannel.TryGetClient($"ID={dtu.DtuId}", out var client))
{
client.WaitHandlePool?.SafeDispose();
client.SafeClose();
_ = client.CloseAsync();
}
}

View File

@@ -150,6 +150,13 @@ public static class LoggerExtensions
logger.Log(TouchSocket.Core.LogLevel.Info, null, msg, null);
}
/// <summary>
/// 输出提示日志
/// </summary>
public static void LogDebug(this ILog logger, string msg)
{
logger.Log(TouchSocket.Core.LogLevel.Debug, null, msg, null);
}
/// <summary>
/// 输出Trace日志
/// </summary>

View File

@@ -83,9 +83,20 @@
"ThingsGateway.Foundation.ConverterConfig": {
"DataFormat": "DataFormat",
"Encoding": "Encoding",
"EncodingName": "字符串编码",
"EncodingName": "EncodingName",
"VariableStringLength": "VariableStringLength",
"Stringlength": "Stringlength",
"BcdFormat": "BcdFormat格式"
"BcdFormat": "BcdFormat"
},
"ThingsGateway.Foundation.DeviceBase": {
"SendDelayTime": "SendDelayTime",
"DataFormat": "DataFormat",
"Timeout": "Timeout",
"IsStringReverseByteWord": "IsStringReverseByteWord"
},
"ThingsGateway.Foundation.DtuServiceDeviceBase": {
"DtuId": "DtuId(UTF8)"
}
}

View File

@@ -88,6 +88,18 @@
"VariableStringLength": "变长字符串",
"Stringlength": "字符串长度",
"BcdFormat": "BCD格式"
},
"ThingsGateway.Foundation.DeviceBase": {
"SendDelayTime": "发送延时",
"DataFormat": "解析规则",
"Timeout": "读写超时",
"IsStringReverseByteWord": "字符串反转"
},
"ThingsGateway.Foundation.DtuServiceDeviceBase": {
"DtuId": "Dtu注册包(UTF8)"
}
}

View File

@@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.4" />
<PackageReference Include="TouchSocket" Version="3.0.25" />
<PackageReference Include="TouchSocket.SerialPorts" Version="3.0.25" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.5" />
<PackageReference Include="TouchSocket" Version="3.1.2" />
<PackageReference Include="TouchSocket.SerialPorts" Version="3.1.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -17,12 +17,12 @@
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.14" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="8.0.14" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.16" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="8.0.16" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="9.0.5" />
</ItemGroup>
<ItemGroup>

View File

@@ -40,9 +40,7 @@ public class AsyncReadWriteLock
private void ReleaseWriter()
{
Interlocked.Decrement(ref _writerCount);
if (Interlocked.Read(ref _writerCount) == 0)
if (Interlocked.Decrement(ref _writerCount) == 0)
{
var resetEvent = _readerLock;
_readerLock = new(false);

View File

@@ -125,7 +125,9 @@ internal sealed class InternalTableColumn(string fieldName, Type fieldType, stri
/// </summary>
public string? RequiredErrorMessage { get; set; }
public bool IsFixedSearchWhenSelect { get; set; } = true;
public int Cols { get; set; }
public bool? IgnoreWhenExport { get; set; }
}

View File

@@ -13,6 +13,7 @@ using BootstrapBlazor.Components;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
@@ -35,6 +36,7 @@ namespace ThingsGateway.Gateway.Application;
[Route("openApi/control")]
[RolePermission]
[LoggingMonitor]
[ApiController]
[Authorize(AuthenticationSchemes = "Bearer")]
public class ControlController : ControllerBase
{
@@ -139,7 +141,7 @@ public class ControlController : ControllerBase
}
}
return await GlobalData.RpcService.InvokeDeviceMethodAsync($"WebApi-{UserManager.UserAccount}-{App.HttpContext.Connection.RemoteIpAddress.MapToIPv4()}", deviceDatas).ConfigureAwait(false);
return await GlobalData.RpcService.InvokeDeviceMethodAsync($"WebApi-{UserManager.UserAccount}-{App.HttpContext?.GetRemoteIpAddressToIPv4()}", deviceDatas).ConfigureAwait(false);
}

View File

@@ -24,6 +24,7 @@ namespace ThingsGateway.Gateway.Application;
[ApiDescriptionSettings("ThingsGateway.OpenApi", Order = 200)]
[DisplayName("数据状态")]
[Route("openApi/runtimeInfo")]
[ApiController]
[RolePermission]
[Authorize(AuthenticationSchemes = "Bearer")]
public class RuntimeInfoController : ControllerBase

View File

@@ -35,6 +35,7 @@ public abstract class BusinessBaseWithCacheAlarmModel<VarModel, DevModel, AlarmM
{
try
{
LogMessage?.LogInformation($"Add {typeof(DevModel).Name} data to file cache, count {data.Count}");
foreach (var item in data)
{
item.Id = CommonUtils.GetSingleId();
@@ -100,6 +101,7 @@ public abstract class BusinessBaseWithCacheAlarmModel<VarModel, DevModel, AlarmM
{
if (_memoryAlarmModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
{
LogMessage?.LogWarning($"{typeof(AlarmModel).Name} Queue exceeds limit, clear old data. If it doesn't work as expected, increase [QueueMaxCount] or Enable cache");
_memoryAlarmModelQueue.Clear();
_memoryAlarmModelQueue.Enqueue(data);
return;

View File

@@ -36,6 +36,7 @@ public abstract class BusinessBaseWithCacheDeviceModel<VarModel, DevModel> : Bus
try
{
LogMessage?.LogInformation($"Add {typeof(DevModel).Name} data to file cache, count {data.Count}");
foreach (var item in data)
{
item.Id = CommonUtils.GetSingleId();
@@ -101,6 +102,7 @@ public abstract class BusinessBaseWithCacheDeviceModel<VarModel, DevModel> : Bus
{
if (_memoryDevModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
{
LogMessage?.LogWarning($"{typeof(DevModel).Name} Queue exceeds limit, clear old data. If it doesn't work as expected, increase [QueueMaxCount] or Enable cache");
_memoryDevModelQueue.Clear();
_memoryDevModelQueue.Enqueue(data);
return;

View File

@@ -39,6 +39,7 @@ public abstract class BusinessBaseWithCacheVariableModel<VarModel> : BusinessBas
{
try
{
LogMessage?.LogInformation($"Add {typeof(VarModel).Name} data to file cache, count {data.Count}");
foreach (var item in data)
{
item.Id = CommonUtils.GetSingleId();
@@ -147,6 +148,7 @@ public abstract class BusinessBaseWithCacheVariableModel<VarModel> : BusinessBas
{
if (_memoryVarModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
{
LogMessage?.LogWarning($"{typeof(VarModel).Name} Queue exceeds limit, clear old data. If it doesn't work as expected, increase [QueueMaxCount] or Enable cache");
_memoryVarModelQueue.Clear();
_memoryVarModelQueue.Enqueue(data);
return;

View File

@@ -38,4 +38,11 @@ public class BusinessPropertyWithCache : BusinessPropertyBase
/// </summary>
[DynamicProperty]
public virtual int QueueMaxCount { get; set; } = 100000;
[DynamicProperty]
public virtual bool OnlineFilter { get; set; } = false;
[DynamicProperty]
public bool GroupUpdate { get; set; } = false;
}

View File

@@ -87,12 +87,12 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
// 触发一次设备状态变化和变量值变化事件
CollectDevices?.ForEach(a =>
{
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine)
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
DeviceStatusChange(a.Value, a.Value.Adapt<DeviceBasicData>());
});
IdVariableRuntimes.ForEach(a =>
{
if (a.Value.IsOnline)
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
});
}
@@ -162,6 +162,8 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
{
if (_exTTimerTick.IsTickHappen())
{
if (LogMessage.LogLevel <= LogLevel.Debug)
LogMessage?.LogDebug($"Interval {typeof(VarModel).Name} data, count {IdVariableRuntimes.Count}");
// 间隔推送全部变量
var variableRuntimes = IdVariableRuntimes.Select(a => a.Value);
VariableTimeInterval(variableRuntimes, variableRuntimes.Adapt<List<VariableBasicData>>());
@@ -177,6 +179,8 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
{
if (CollectDevices != null)
{
if (LogMessage.LogLevel <= LogLevel.Debug)
LogMessage?.LogDebug($"Interval {typeof(DevModel).Name} data, count {CollectDevices.Count}");
// 间隔推送全部设备
foreach (var deviceRuntime in CollectDevices.Select(a => a.Value))

View File

@@ -85,12 +85,12 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
CollectDevices?.ForEach(a =>
{
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine)
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
DeviceStatusChange(a.Value, a.Value.Adapt<DeviceBasicData>());
});
IdVariableRuntimes.ForEach(a =>
{
if (a.Value.IsOnline)
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
});
}
@@ -152,6 +152,8 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
{
if (_exTTimerTick.IsTickHappen())
{
if (LogMessage.LogLevel <= LogLevel.Debug)
LogMessage?.LogDebug($"Interval {typeof(VarModel).Name} data, count {IdVariableRuntimes.Count}");
// 上传所有变量信息
var variableRuntimes = IdVariableRuntimes.Select(a => a.Value);
VariableTimeInterval(variableRuntimes, variableRuntimes.Adapt<List<VariableBasicData>>());
@@ -168,6 +170,8 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
{
if (CollectDevices != null)
{
if (LogMessage.LogLevel <= LogLevel.Debug)
LogMessage?.LogDebug($"Interval {typeof(DevModel).Name} data, count {CollectDevices.Count}");
// 上传所有设备信息
foreach (var deviceRuntime in CollectDevices.Select(a => a.Value))
{

View File

@@ -9,6 +9,9 @@
//------------------------------------------------------------------------------
using System.Globalization;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.RegularExpressions;
using ThingsGateway.NewLife.Json.Extension;
@@ -37,7 +40,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
List<string> strings = new List<string>();
// 使用正则表达式查找输入字符串中的所有匹配项
Regex regex = new Regex(@"\$\{(.+?)\}");
Regex regex = TopicRegex();
MatchCollection matches = regex.Matches(input);
// 遍历匹配结果,将匹配到的字符串添加到列表中
@@ -63,7 +66,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
protected List<TopicJson> GetAlarms(IEnumerable<AlarmModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
List<TopicJson> topicJsonList = new List<TopicJson>();
var topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic);
if (topics.Count > 0)
{
@@ -86,10 +89,12 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
{
// 如果是报警列表,则将整个分组转换为 JSON 字符串
string json = group.Select(a => a).ToList().ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
var gList = group.Select(a => a).ToList();
string json = gList.ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicJsonList.Add(new(topic, json, gList.Count));
}
else
{
@@ -98,7 +103,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
string json = JsonExtensions.ToJsonNetString(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicJsonList.Add(new(topic, json, 1));
}
}
}
@@ -108,15 +113,16 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
{
string json = data.Select(a => a).ToList().ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json));
var gList = data.Select(a => a).ToList();
string json = gList.ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, gList.Count));
}
else
{
foreach (var group in data)
{
string json = JsonExtensions.ToJsonNetString(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json));
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, 1));
}
}
}
@@ -128,7 +134,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
protected List<TopicJson> GetDeviceData(IEnumerable<DevModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
List<TopicJson> topicJsonList = new List<TopicJson>();
var topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic);
if (topics.Count > 0)
{
@@ -153,9 +159,10 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
{
// 如果是设备列表,则将整个分组转换为 JSON 字符串
string json = group.Select(a => a).ToList().ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
var gList = group.Select(a => a).ToList();
string json = gList.ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicJsonList.Add(new(topic, json, gList.Count));
}
else
{
@@ -164,7 +171,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
string json = JsonExtensions.ToJsonNetString(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicJsonList.Add(new(topic, json, 1));
}
}
}
@@ -175,15 +182,16 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
{
string json = data.Select(a => a).ToList().ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json));
var gList = data.Select(a => a).ToList();
string json = gList.ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, gList.Count));
}
else
{
foreach (var group in data)
{
string json = JsonExtensions.ToJsonNetString(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json));
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, 1));
}
}
}
@@ -193,7 +201,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
protected List<TopicJson> GetVariable(IEnumerable<VarModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
List<TopicJson> topicJsonList = new List<TopicJson>();
var topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
if (topics.Count > 0)
{
@@ -218,9 +226,10 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
// 如果是变量列表,则将整个分组转换为 JSON 字符串
string json = group.Select(a => a).ToList().ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
var gList = group.Select(a => a).ToList();
string json = gList.ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicJsonList.Add(new(topic, json, gList.Count));
}
else
{
@@ -229,7 +238,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
string json = JsonExtensions.ToJsonNetString(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicJsonList.Add(new(topic, json, 1));
}
}
}
@@ -240,15 +249,16 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
string json = data.Select(a => a).ToList().ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
var gList = data.Select(a => a).ToList();
string json = gList.ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count));
}
else
{
foreach (var group in data)
{
string json = JsonExtensions.ToJsonNetString(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1));
}
}
}
@@ -267,7 +277,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
data = item;
}
List<TopicJson> topicJsonList = new List<TopicJson>();
var topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
if (topics.Count > 0)
{
@@ -291,10 +301,11 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
// 如果是变量列表,则将整个分组转换为 JSON 字符串
string json = group.Select(a => a).GroupBy(a => a.DeviceName, b => b).ToDictionary(a => a.Key, b => b.ToList()).ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicJsonList.Add(new(topic, json, group.Count()));
}
else
{
@@ -303,7 +314,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
string json = JsonExtensions.ToJsonNetString(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicJsonList.Add(new(topic, json, 1));
}
}
}
@@ -315,24 +326,58 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
string json = data.Select(a => a).GroupBy(a => a.DeviceName, b => b).ToDictionary(a => a.Key, b => b.ToList()).ToJsonNetString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, data.Count()));
}
else
{
foreach (var group in data)
{
string json = JsonExtensions.ToJsonNetString(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1));
}
}
}
return topicJsonList;
}
//protected static byte[] Serialize(object value)
//{
// var block = new ValueByteBlock(1024 * 64);
// try
// {
// //将数据序列化到内存块
// FastBinaryFormatter.Serialize(ref block, value);
// block.SeekToStart();
// return block.Memory.GetArray().Array;
// }
// finally
// {
// block.Dispose();
// }
//}
protected static JsonSerializerOptions NoWriteIndentedJsonSerializerOptions = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
};
protected static JsonSerializerOptions WriteIndentedJsonSerializerOptions = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
};
protected static byte[] Serialize(object data, bool writeIndented)
{
if (data == null) return Array.Empty<byte>();
byte[] payload = JsonSerializer.SerializeToUtf8Bytes(data, data.GetType(), writeIndented ? WriteIndentedJsonSerializerOptions : NoWriteIndentedJsonSerializerOptions);
return payload;
}
protected List<TopicArray> GetAlarmTopicArrays(IEnumerable<AlarmModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
List<TopicArray> topicJsonList = new List<TopicArray>();
List<TopicArray> topicArrayList = new List<TopicArray>();
var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic);
if (topics.Count > 0)
{
@@ -355,18 +400,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
{
var json = Serialize(group.Select(a => a).ToList().ToList());
var gList = group.Select(a => a).ToList();
var json = Serialize(gList, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicArrayList.Add(new(topic, json, gList.Count));
}
else
{
// 如果不是报警列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = Serialize(gro);
var json = Serialize(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicArrayList.Add(new(topic, json, 1));
}
}
}
@@ -376,42 +422,27 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
{
var json = Serialize(data.Select(a => a).ToList());
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json));
var gList = data.Select(a => a).ToList();
var json = Serialize(gList, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, gList.Count));
}
else
{
foreach (var group in data)
{
var json = Serialize(group);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json));
var json = Serialize(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, 1));
}
}
}
return topicJsonList;
}
protected static ArraySegment<byte> Serialize(object value)
{
var block = new ValueByteBlock(1024 * 64);
try
{
//将数据序列化到内存块
FastBinaryFormatter.Serialize(ref block, value);
block.SeekToStart();
return block.Memory.GetArray();
}
finally
{
block.Dispose();
}
return topicArrayList;
}
protected List<TopicArray> GetDeviceTopicArray(IEnumerable<DevModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
List<TopicArray> topicJsonList = new List<TopicArray>();
List<TopicArray> topicArrayList = new List<TopicArray>();
var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic);
if (topics.Count > 0)
{
@@ -436,18 +467,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
{
// 如果是设备列表,则将整个分组转换为 JSON 字符串
var json = Serialize(group.Select(a => a).ToList());
var gList = group.Select(a => a).ToList();
var json = Serialize(gList, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicArrayList.Add(new(topic, json, gList.Count));
}
else
{
// 如果不是设备列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = Serialize(gro);
var json = Serialize(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicArrayList.Add(new(topic, json, 1));
}
}
}
@@ -458,25 +490,26 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
{
var json = Serialize(data.Select(a => a).ToList());
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json));
var gList = data.Select(a => a).ToList();
var json = Serialize(gList, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, gList.Count));
}
else
{
foreach (var group in data)
{
var json = Serialize(group);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json));
var json = Serialize(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, 1));
}
}
}
return topicJsonList;
return topicArrayList;
}
protected List<TopicArray> GetVariableTopicArray(IEnumerable<VarModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
List<TopicArray> topicJsonList = new List<TopicArray>();
List<TopicArray> topicArrayList = new List<TopicArray>();
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
if (topics.Count > 0)
{
@@ -501,18 +534,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
// 如果是变量列表,则将整个分组转换为 JSON 字符串
var json = Serialize(group.Select(a => a).ToList());
var gList = group.Select(a => a).ToList();
var json = Serialize(gList, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicArrayList.Add(new(topic, json, gList.Count));
}
else
{
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = Serialize(gro);
var json = Serialize(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicArrayList.Add(new(topic, json, 1));
}
}
}
@@ -523,22 +557,22 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
var json = Serialize(data.Select(a => a).ToList());
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
var gList = data.Select(a => a).ToList();
var json = Serialize(gList, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count));
}
else
{
foreach (var group in data)
{
var json = Serialize(group);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
var json = Serialize(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1));
}
}
}
return topicJsonList;
return topicArrayList;
}
protected List<TopicArray> GetVariableBasicDataTopicArray(IEnumerable<VariableBasicData> item)
{
IEnumerable<VariableBasicData>? data = null;
@@ -551,7 +585,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
data = item;
}
List<TopicArray> topicJsonList = new List<TopicArray>();
List<TopicArray> topicArrayList = new List<TopicArray>();
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
if (topics.Count > 0)
{
@@ -576,18 +610,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
// 如果是变量列表,则将整个分组转换为 JSON 字符串
var json = Serialize(group.Select(a => a).ToList());
var gList = group.Select(a => a).ToList();
var json = Serialize(gList, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicArrayList.Add(new(topic, json, gList.Count));
}
else
{
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = Serialize(gro);
var json = Serialize(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
topicArrayList.Add(new(topic, json, 1));
}
}
}
@@ -598,21 +633,40 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
var json = Serialize(data.Select(a => a).ToList());
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
var gList = data.Select(a => a).ToList();
var json = Serialize(gList, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count));
}
else
{
foreach (var group in data)
{
var json = Serialize(group);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
var json = Serialize(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1));
}
}
}
return topicJsonList;
return topicArrayList;
}
protected string GetDetailLogString(TopicArray topicArray, int queueCount)
{
if (queueCount > 0)
return $"Up Topic{topicArray.Topic}{Environment.NewLine}PayLoad{Encoding.UTF8.GetString(topicArray.Json)} {Environment.NewLine} VarModelQueue:{queueCount}";
else
return $"Up Topic{topicArray.Topic}{Environment.NewLine}PayLoad{Encoding.UTF8.GetString(topicArray.Json)}";
}
protected string GetCountLogString(TopicArray topicArray, int queueCount)
{
if (queueCount > 0)
return $"Up Topic{topicArray.Topic}{Environment.NewLine}Count{topicArray.Count} {Environment.NewLine} VarModelQueue:{queueCount}";
else
return $"Up Topic{topicArray.Topic}{Environment.NewLine}Count{topicArray.Count}";
}
[GeneratedRegex(@"\$\{(.+?)\}")]
private static partial Regex TopicRegex();
#endregion
}

View File

@@ -18,10 +18,10 @@ using TouchSocket.Core;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 抽象类 <see cref="BusinessBaseWithCacheIntervalVariableModel{T}"/>,表示具有缓存间隔功能的业务基类,其中 T 代表变量模型。
/// 抽象类 <see cref="BusinessBaseWithCacheIntervalVariableModel{VarModel}"/>,表示具有缓存间隔功能的业务基类,其中 T 代表变量模型。
/// </summary>
/// <typeparam name="T">变量模型类型</typeparam>
public abstract class BusinessBaseWithCacheIntervalVariableModel<T> : BusinessBaseWithCacheVariableModel<T>
/// <typeparam name="VarModel">变量模型类型</typeparam>
public abstract class BusinessBaseWithCacheIntervalVariableModel<VarModel> : BusinessBaseWithCacheVariableModel<VarModel>
{
/// <summary>
/// 用于定时触发的时间间隔。
@@ -73,7 +73,7 @@ public abstract class BusinessBaseWithCacheIntervalVariableModel<T> : BusinessBa
// 触发一次变量值变化事件
IdVariableRuntimes.ForEach(a =>
{
if (a.Value.IsOnline)
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
});
}
@@ -110,6 +110,8 @@ public abstract class BusinessBaseWithCacheIntervalVariableModel<T> : BusinessBa
{
if (_exTTimerTick.IsTickHappen())
{
if (LogMessage.LogLevel <= LogLevel.Debug)
LogMessage?.LogDebug($"Interval {typeof(VarModel).Name} data, count {IdVariableRuntimes.Count}");
//间隔推送全部变量
var variableRuntimes = IdVariableRuntimes.Select(a => a.Value);
VariableTimeInterval(variableRuntimes, variableRuntimes.Adapt<List<VariableBasicData>>());

View File

@@ -1,32 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// <inheritdoc/>
/// </summary>
public class BusinessPropertyWithCacheIntervalDBScript : BusinessPropertyWithCacheInterval
{
/// <summary>
/// 表定义实体脚本
/// </summary>
[DynamicProperty]
[AutoGenerateColumn(ComponentType = typeof(Textarea), Rows = 3)]
public string? BigTextScriptTableModel { get; set; }
/// <summary>
/// 数据脚本
/// </summary>
[AutoGenerateColumn(ComponentType = typeof(Textarea), Rows = 3)]
public string? BigTextScriptDataModel { get; set; }
}

View File

@@ -17,6 +17,11 @@ namespace ThingsGateway.Gateway.Application;
/// </summary>
public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCacheInterval
{
/// <summary>
/// 是否显示详细日志
/// </summary>
[DynamicProperty]
public bool DetailLog { get; set; } = true;
/// <summary>
/// 缩进格式化

View File

@@ -12,11 +12,13 @@ namespace ThingsGateway.Gateway.Application;
public struct TopicArray
{
public TopicArray(string topic, ArraySegment<byte> json)
public TopicArray(string topic, byte[] json, int count)
{
Topic = topic; Json = json;
Topic = topic; Json = json; Count = count;
}
public ArraySegment<byte> Json { get; set; }
public int Count { get; set; } = 1;
public byte[] Json { get; set; }
public string Topic { get; set; }
}

View File

@@ -12,10 +12,11 @@ namespace ThingsGateway.Gateway.Application;
public struct TopicJson
{
public TopicJson(string topic, string json)
public TopicJson(string topic, string json, int count)
{
Topic = topic; Json = json;
Topic = topic; Json = json; Count = count;
}
public int Count { get; set; } = 1;
public string Json { get; set; }
public string Topic { get; set; }

View File

@@ -0,0 +1,87 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 插件配置项
/// <br></br>
/// 使用<see cref="DynamicPropertyAttribute"/> 标识所需的配置属性
/// </summary>
public abstract class CollectFoundationPackPropertyBase : CollectFoundationPropertyBase
{
/// <summary>
/// 最大打包长度
/// </summary>
[DynamicProperty]
public ushort MaxPack { get; set; } = 100;
}
public abstract class CollectFoundationPropertyBase : CollectPropertyRetryBase
{
/// <summary>
/// 读写超时时间
/// </summary>
[DynamicProperty]
public virtual ushort Timeout { get; set; } = 3000;
/// <summary>
/// 帧前时间ms
/// </summary>
[DynamicProperty]
public virtual int SendDelayTime { get; set; } = 0;
[DynamicProperty]
public bool IsStringReverseByteWord { get; set; }
/// <summary>
/// 默认解析顺序
/// </summary>
[DynamicProperty]
public virtual DataFormatEnum DataFormat { get; set; }
}
/// <summary>
/// 插件配置项
/// <br></br>
/// 使用<see cref="DynamicPropertyAttribute"/> 标识所需的配置属性
/// </summary>
public abstract class CollectFoundationDtuPropertyBase : CollectFoundationPropertyBase
{
/// <summary>
/// 默认DtuId
/// </summary>
[DynamicProperty]
public string? DtuId { get; set; }
}
/// <summary>
/// 插件配置项
/// <br></br>
/// 使用<see cref="DynamicPropertyAttribute"/> 标识所需的配置属性
/// </summary>
public abstract class CollectFoundationDtuPackPropertyBase : CollectFoundationPackPropertyBase
{
/// <summary>
/// 默认DtuId
/// </summary>
[DynamicProperty]
public string? DtuId { get; set; }
}

View File

@@ -25,12 +25,30 @@ public abstract class CollectPropertyBase : DriverPropertyBase
/// <summary>
/// 离线后恢复运行的间隔时间
/// </summary>
[DynamicProperty]
public virtual int ReIntervalTime { get; set; } = 0;
/// <summary>
/// 失败重试次数默认3
/// </summary>
[DynamicProperty]
public virtual int RetryCount { get; set; } = 3;
}
/// <summary>
/// 插件配置项
/// <br></br>
/// 使用<see cref="DynamicPropertyAttribute"/> 标识所需的配置属性
/// </summary>
public abstract class CollectPropertyRetryBase : CollectPropertyBase
{
/// <summary>
/// 离线后恢复运行的间隔时间
/// </summary>
[DynamicProperty]
public override int ReIntervalTime { get; set; } = 0;
/// <summary>
/// 失败重试次数默认3
/// </summary>
[DynamicProperty]
public override int RetryCount { get; set; } = 3;
}

View File

@@ -140,7 +140,7 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
/// <summary>
/// 缓存超时
/// </summary>
[SugarColumn(ColumnDescription = "缓存超时")]
[SugarColumn(ColumnDescription = "缓存超时", IsNullable = true, DefaultValue = "500")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[MinValue(100)]
public override int CacheTimeout { get; set; } = 500;
@@ -148,7 +148,7 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
/// <summary>
/// 连接超时
/// </summary>
[SugarColumn(ColumnDescription = "连接超时")]
[SugarColumn(ColumnDescription = "连接超时", IsNullable = true, DefaultValue = "3000")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[MinValue(100)]
public override ushort ConnectTimeout { get; set; } = 3000;
@@ -156,33 +156,36 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
/// <summary>
/// 最大并发数
/// </summary>
[SugarColumn(ColumnDescription = "最大并发数")]
[SugarColumn(ColumnDescription = "最大并发数", IsNullable = true, DefaultValue = "1")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[MinValue(1)]
public override int MaxConcurrentCount { get; set; } = 1;
[SugarColumn(ColumnDescription = "最大连接数")]
[SugarColumn(ColumnDescription = "最大连接数", IsNullable = true, DefaultValue = "10000")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override int MaxClientCount { get; set; } = 10000;
[SugarColumn(ColumnDescription = "客户端滑动过期时间")]
[SugarColumn(ColumnDescription = "客户端滑动过期时间", IsNullable = true, DefaultValue = "120000")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override int CheckClearTime { get; set; } = 120000;
[SugarColumn(ColumnDescription = "心跳内容", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override string Heartbeat { get; set; } = "Heartbeat";
#region dtu终端
[SugarColumn(ColumnDescription = "心跳间隔")]
[SugarColumn(ColumnDescription = "心跳间隔", IsNullable = true, DefaultValue = "60000")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override int HeartbeatTime { get; set; } = 60000;
[SugarColumn(ColumnDescription = "DtuId", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override string DtuId { get; set; }
#endregion
[SugarColumn(ColumnDescription = "Dtu类型")]
[SugarColumn(ColumnDescription = "Dtu类型", IsNullable = true, DefaultValue = "0")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override DtuSeviceType DtuSeviceType { get; set; }

View File

@@ -56,7 +56,7 @@ public class Device : BaseDataEntity, IValidatableObject
/// <summary>
/// 通道
/// </summary>
[SugarColumn(ColumnDescription = "通道", Length = 200)]
[SugarColumn(ColumnDescription = "通道")]
[AutoGenerateColumn(Ignore = true)]
[IgnoreExcel]
[MinValue(1)]

View File

@@ -28,7 +28,7 @@ namespace ThingsGateway.Gateway.Application;
[SugarTable("variable", TableDescription = "设备变量表")]
[Tenant(SqlSugarConst.DB_Custom)]
[SugarIndex("index_device", nameof(Variable.DeviceId), OrderByType.Asc)]
[SugarIndex("unique_variable_name", nameof(Variable.Name), OrderByType.Asc, nameof(Variable.DeviceId), OrderByType.Asc, true)]
[SugarIndex("unique_deviceid_variable_name", nameof(Variable.Name), OrderByType.Asc, nameof(Variable.DeviceId), OrderByType.Asc, true)]
public class Variable : BaseDataEntity, IValidatableObject
{
@@ -51,6 +51,13 @@ public class Variable : BaseDataEntity, IValidatableObject
[Required]
public virtual string Name { get; set; }
/// <summary>
/// 采集组
/// </summary>
[SugarColumn(ColumnDescription = "采集组", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 1)]
public virtual string CollectGroup { get; set; } = string.Empty;
/// <summary>
/// 分组名称
/// </summary>

View File

@@ -1,8 +1,76 @@
{
"ThingsGateway.Gateway.Application.CollectFoundationDtuPackPropertyBase": {
"DtuId": "DtuId(UTF8)"
},
"ThingsGateway.Gateway.Application.CollectFoundationDtuPropertyBase": {
"DtuId": "DtuId(UTF8)"
},
"ThingsGateway.Gateway.Application.CollectFoundationPackPropertyBase": {
"MaxPack": "MaxPack"
},
"ThingsGateway.Gateway.Application.CollectFoundationPropertyBase": {
"DataFormat": "DataFormat",
"IsStringReverseByteWord": "IsStringReverseByteWord",
"Timeout": "Timeout",
"SendDelayTime": "SendDelayTime"
},
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
"ReIntervalTime": "ReIntervalTime",
"RetryCount": "RetryCount"
},
"ThingsGateway.Gateway.Application.CollectPropertyBase": {
"ReIntervalTime": "ReIntervalTime",
"RetryCount": "RetryCount",
"ConcurrentCount": "ConcurrentCount"
},
"ThingsGateway.Gateway.Application.BusinessPropertyWithCacheIntervalScript": {
"DetailLog": "DetailLog",
"JsonFormattingIndented": "JsonFormattingIndented",
"BigTextScriptDeviceModel": "BigTextScriptDeviceModel",
"BigTextScriptVariableModel": "BigTextScriptVariableModel",
"BigTextScriptAlarmModel": "BigTextScriptAlarmModel",
"IsDeviceList": "IsDeviceList",
"IsVariableList": "IsVariableList",
"IsAlarmList": "IsAlarmList",
"DeviceTopic": "DeviceTopic",
"VariableTopic": "VariableTopic",
"AlarmTopic": "AlarmTopic"
},
"ThingsGateway.Gateway.Application.BusinessPropertyWithCacheInterval": {
"BusinessUpdateEnum": "BusinessUpdateEnum",
"BusinessInterval": "BusinessInterval",
"IsAllVariable": "IsAllVariable"
},
"ThingsGateway.Gateway.Application.BusinessPropertyWithCache": {
"QueueMaxCount": "QueueMaxCount",
"CacheFileMaxLength": "CacheFileMaxLength",
"SplitSize": "SplitSize",
"CacheEnable": "CacheEnable",
"GroupUpdate": "GroupUpdate",
"OnlineFilter": "OnlineFilter"
},
"ThingsGateway.Gateway.Application.BusinessUpdateEnum": {
"Change": "Change",
"Interval": "Interval",
"IntervalOrChange": "IntervalOrChange"
},
"ThingsGateway.Gateway.Application.ControlController": {
@@ -30,55 +98,17 @@
"ActiveTime": "ActiveTime",
"DeviceVariableCount": "DeviceVariableCount",
"LastErrorMessage": "LastErrorMessage",
"Name": "Name",
"DeviceStatus": "DeviceStatus",
"RedundantType": "RedundantType",
"PluginName": "PluginName",
"Name.Required": " {0} cannot be empty",
"Description": "Description",
"ChannelId": "Channel",
"ChannelName": "ChannelName",
"IntervalTime": "IntervalTime",
"IntervalTime.MinValue": " {0} value is too small",
"PluginName": "PluginName",
"PluginName.Required": "{0} cannot be empty",
"LogEnable": "LogEnable",
"LogLevel": "LogLevel",
"RedundantSwitchType": "RedundantSwitchType",
"RedundantScanIntervalTime": "RedundantScanIntervalTime",
"RedundantScript": "RedundantScript",
"Enable": "Enable",
"RedundantEnable": "RedundantEnable",
"RedundantDeviceId": "RedundantDevice",
"Remark1": "Remark1",
"Remark2": "Remark2",
"Remark3": "Remark3",
"Remark4": "Remark4",
"Remark5": "Remark5",
"RedundantDeviceNotNull": "When enabling redundancy, you must select a redundant device",
"SaveDevice": "Add/Modify Device",
"CopyDevice": "Copy Device",
"DeleteDevice": "Delete Device",
"ClearDevice": "Clear Device",
"ExportDevice": "Export Device",
"ImportDevice": "Import Device",
"ImportNullError": "Unable to recognize",
"RedundantDeviceError": "Redundant device error",
"ChannelError": "Channel error",
"NotNull": "{0} does not exist",
"DeviceNotNull": "Device name does not exist",
"NameDump": "Duplicate device name {0}",
"PluginName.Required": "{0} cannot be empty"
"DeviceStatus": "DeviceStatus",
"PluginNotNull": "Plugin name does not exist"
},
@@ -94,59 +124,11 @@
"Value": "Value",
"AlarmEnable": "AlarmEnable",
"Name": "Name",
"Description": "Description",
"Group": "Group",
"DeviceId": "CollectionDevice",
"DeviceId.MinValue": "{0} cannot be empty",
"DeviceId.Required": "{0} cannot be empty",
"IntervalTime": "IntervalTime",
"IntervalTime.MinValue": "{0} value is too small",
"Enable": "Enable",
"ProtectType": "ProtectType",
"DataType": "DataType",
"ReadExpressions": "ReadExpressions",
"WriteExpressions": "WriteExpressions",
"RpcWriteEnable": "RpcWriteEnable",
"SaveValue": "SaveValue",
"DynamicVariable": "DynamicVariable",
"ArrayLength": "ArrayLength",
"AlarmDelay": "AlarmDelay",
"BoolOpenAlarmEnable": "BoolOpenAlarmEnable",
"BoolOpenRestrainExpressions": "BoolOpenRestrainExpressions",
"BoolOpenAlarmText": "BoolOpenAlarmText",
"BoolCloseAlarmEnable": "BoolCloseAlarmEnable",
"BoolCloseRestrainExpressions": "BoolCloseRestrainExpressions",
"BoolCloseAlarmText": "BoolCloseAlarmText",
"HAlarmEnable": "HAlarmEnable",
"HRestrainExpressions": "HRestrainExpressions",
"HAlarmText": "HAlarmText",
"HAlarmCode": "HAlarmCode",
"HHAlarmEnable": "HHAlarmEnable",
"HHRestrainExpressions": "HHRestrainExpressions",
"HHAlarmText": "HHAlarmText",
"HHAlarmCode": "HHAlarmCode",
"LAlarmEnable": "LAlarmEnable",
"LRestrainExpressions": "LRestrainExpressions",
"LAlarmText": "LAlarmText",
"LAlarmCode": "LAlarmCode",
"LLAlarmEnable": "LLAlarmEnable",
"LLRestrainExpressions": "LLRestrainExpressions",
"LLAlarmText": "LLAlarmText",
"LLAlarmCode": "LLAlarmCode",
"CustomAlarmEnable": "CustomAlarmEnable",
"CustomRestrainExpressions": "CustomRestrainExpressions",
"CustomAlarmText": "CustomAlarmText",
"CustomAlarmCode": "CustomAlarmCode",
"Unit": "Unit",
"RegisterAddress": "RegisterAddress",
"OtherMethod": "OtherMethod",
"Remark1": "Remark1",
"Remark2": "Remark2",
"Remark3": "Remark3",
"Remark4": "Remark4",
"Remark5": "Remark5",
"AlarmCode": "AlarmCode",
"RecoveryCode": "RecoveryCode",
"AlarmLimit": "AlarmLimit",
@@ -170,10 +152,7 @@
"Name": "Name",
"Description": "Description",
"DeviceId": "CollectionDevice",
"DeviceId.MinValue": "{0} cannot be empty",
"DeviceId.Required": "{0} cannot be empty",
"IntervalTime": "IntervalTime",
"IntervalTime.MinValue": "{0} value is too small",
"Enable": "Enable",
"ProtectType": "ProtectType",
"DataType": "DataType",
@@ -348,51 +327,21 @@
"NameDump": "Duplicate device name {0}"
},
"ThingsGateway.Gateway.Application.ChannelRuntime": {
"Name": "Name",
"Name.Required": "{0} cannot be empty",
"ChannelType": "ChannelType",
"PluginName": "PluginName",
"PluginName.Required": "{0} cannot be empty",
"LogLevel": "LogLevel",
"Enable": "Enable",
"LogEnable": "LogEnable",
"RemoteUrl": "RemoteUrl",
"BindUrl": "BindUrl",
"PortName": "PortName",
"BaudRate": "BaudRate",
"DataBits": "DataBits",
"Parity": "Parity",
"StopBits": "StopBits",
"DtrEnable": "DtrEnable",
"RtsEnable": "RtsEnable",
"CacheTimeout": "CacheTimeout",
"ConnectTimeout": "ConnectTimeout",
"MaxConcurrentCount": "MaxConcurrentCount",
"PluginType": "PluginType",
"DeviceRuntimeCount": "DeviceRuntimeCount",
"MaxConcurrentCount": "MaxConcurrentCount"
"MaxClientCount": "MaxClientCount",
"CheckClearTime": "CheckClearTime",
"Heartbeat": "Heartbeat",
"HeartbeatTime": "HeartbeatTime",
"DtuId": "DtuId",
"DtuSeviceType": "DtuSeviceType",
"SaveChannel": "Add/Modify Channel",
"CopyChannel": "Copy Channel",
"DeleteChannel": "Delete Channel",
"ClearChannel": "Clear Channel",
"ExportChannel": "Export Channel",
"ImportChannel": "Import Channel",
"ImportNullError": "Unable to recognize",
"NotOther": "Not supporting other channel types",
"Connect": "Connect",
"Confim": "Confim",
"Disconnect": "Disconnect",
"Channel": "Channel",
"NameDump": "Duplicate channel name {0}"
},
"ThingsGateway.Gateway.Application.Channel": {
"SortCode": "SortCode",
"CreateTime": "CreateTime",
"CreateUser": "CreateUser",
"UpdateTime": "UpdateTime",
"UpdateUser": "UpdateUser",
"Name": "Name",
"Name.Required": "{0} cannot be empty",
"ChannelType": "ChannelType",
@@ -452,6 +401,7 @@
"Name.Required": "{0} cannot be empty",
"Description": "Description",
"Group": "Group",
"CollectGroup": "CollectGroup",
"DeviceId": "CollectionDevice",
"DeviceId.MinValue": "{0} cannot be empty",
"DeviceId.Required": "{0} cannot be empty",

View File

@@ -1,4 +1,70 @@
{
"ThingsGateway.Gateway.Application.CollectFoundationDtuPackPropertyBase": {
"DtuId": "Dtu注册包(UTF8)"
},
"ThingsGateway.Gateway.Application.CollectFoundationDtuPropertyBase": {
"DtuId": "Dtu注册包(UTF8)"
},
"ThingsGateway.Gateway.Application.CollectFoundationPackPropertyBase": {
"MaxPack": "最大打包长度"
},
"ThingsGateway.Gateway.Application.CollectFoundationPropertyBase": {
"DataFormat": "解析规则",
"IsStringReverseByteWord": "字符串反转",
"Timeout": "读写超时时间",
"SendDelayTime": "发送延时"
},
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
"ReIntervalTime": "离线恢复时间",
"RetryCount": "失败重试次数"
},
"ThingsGateway.Gateway.Application.CollectPropertyBase": {
"ReIntervalTime": "离线恢复时间",
"RetryCount": "失败重试次数",
"ConcurrentCount": "最大并发数量"
},
"ThingsGateway.Gateway.Application.BusinessPropertyWithCacheIntervalScript": {
"DetailLog": "详细日志",
"JsonFormattingIndented": "Json缩进格式化",
"BigTextScriptDeviceModel": "设备上传脚本",
"BigTextScriptVariableModel": "变量上传脚本",
"BigTextScriptAlarmModel": "报警上传脚本",
"IsDeviceList": "设备状态列表上传",
"IsVariableList": "变量列表上传",
"IsAlarmList": "报警列表上传",
"DeviceTopic": "设备主题",
"VariableTopic": "变量主题",
"AlarmTopic": "报警主题"
},
"ThingsGateway.Gateway.Application.BusinessPropertyWithCacheInterval": {
"BusinessUpdateEnum": "上传模式",
"BusinessInterval": "定时上传间隔",
"IsAllVariable": "选择全部变量"
},
"ThingsGateway.Gateway.Application.BusinessPropertyWithCache": {
"QueueMaxCount": "内存队列最大数量",
"CacheFileMaxLength": "缓存文件最大长度(mb)",
"SplitSize": "上传每页条数",
"CacheEnable": "启用缓存",
"GroupUpdate": "分组上传",
"OnlineFilter": "过滤离线变量"
},
"ThingsGateway.Gateway.Application.BusinessUpdateEnum": {
"Change": "变化",
"Interval": "定时",
@@ -29,50 +95,16 @@
"ActiveTime": "活跃时间",
"DeviceVariableCount": "变量数量",
"LastErrorMessage": "离线原因",
"Name": "名称",
"DeviceStatus": "设备状态",
"RedundantType": "冗余状态",
"PluginName": "插件名称",
"Name.Required": " {0} 不可为空",
"Description": "描述",
"ChannelId": "通道",
"ChannelName": "通道",
"ChannelId.MinValue": " {0} 不可为空",
"ChannelId.Required": " {0} 不可为空",
"IntervalTime": "默认执行间隔",
"IntervalTime.MinValue": " {0} 值太小",
"PluginName": "插件名称",
"PluginName.Required": "{0} 插件名称不可为空",
"PluginName.Required": "{0} 插件名称不可为空"
"Enable": "设备使能",
"LogEnable": "启用调试日志",
"LogLevel": "日志等级",
"RedundantEnable": "启用冗余",
"RedundantDeviceId": "冗余设备",
"RedundantSwitchType": "冗余操作模式",
"RedundantScanIntervalTime": "冗余检测时间",
"RedundantScript": "冗余检测脚本",
"Remark1": "备用1",
"Remark2": "备用2",
"Remark3": "备用3",
"Remark4": "备用4",
"Remark5": "备用5",
"RedundantDeviceNotNull": "启用冗余时,必须选择备用设备",
"SaveDevice": "添加/修改设备",
"CopyDevice": "复制设备",
"DeleteDevice": "删除设备",
"ClearDevice": "清空设备",
"ExportDevice": "导出设备",
"ImportDevice": "导入设备",
"DeviceStatus": "设备状态",
"ImportNullError": "无法识别",
"RedundantDeviceError": "冗余设备错误",
"ChannelError": "通道错误",
"NotNull": " {0} 不存在",
"DeviceNotNull": "设备名称不存在",
"PluginNotNull": "插件不存在"
},
"ThingsGateway.Gateway.Application.VariableRuntime": {
"ChangeTime": "变化时间",
@@ -85,71 +117,14 @@
"Value": "实时值",
"RuntimeType": "动态类型",
"AlarmEnable": "报警使能",
"Name": "名称",
"SortCode": "排序",
"Name.Required": " {0} 不可为空",
"Description": "描述",
"Group": "分组",
"DeviceId": "采集设备",
"DeviceId.MinValue": " {0} 不可为空",
"DeviceId.Required": " {0} 不可为空",
"IntervalTime": "执行间隔",
"IntervalTime.MinValue": " {0} 值太小",
"ArrayLength": "数组长度",
"Enable": "变量使能",
"ProtectType": "读写权限",
"DataType": "数据类型",
"ReadExpressions": "读取表达式",
"WriteExpressions": "写入表达式",
"RpcWriteEnable": "远程写入",
"SaveValue": "保存初始值",
"DynamicVariable": "动态变量",
"AlarmDelay": "报警延时",
"BoolOpenAlarmEnable": "布尔开报警使能",
"BoolOpenRestrainExpressions": "布尔开报警约束",
"BoolOpenAlarmText": "布尔开报警文本",
"BoolCloseAlarmEnable": "布尔关报警使能",
"BoolCloseRestrainExpressions": "布尔关报警约束",
"BoolCloseAlarmText": "布尔关报警文本",
"HAlarmEnable": "高报使能",
"HRestrainExpressions": "高报约束",
"HAlarmText": "高报文本",
"HAlarmCode": "高限值",
"HHAlarmEnable": "高高报使能",
"HHRestrainExpressions": "高高报约束",
"HHAlarmText": "高高报文本",
"HHAlarmCode": "高高限值",
"LAlarmEnable": "低报使能",
"LRestrainExpressions": "低报约束",
"LAlarmText": "低报文本",
"LAlarmCode": "低限值",
"LLAlarmEnable": "低低报使能",
"LLRestrainExpressions": "低低报约束",
"LLAlarmText": "低低报文本",
"LLAlarmCode": "低低限值",
"CustomAlarmEnable": "自定义报警使能",
"CustomRestrainExpressions": "自定义报警约束",
"CustomAlarmText": "自定义报警文本",
"CustomAlarmCode": "自定义报警限值",
"Unit": "单位",
"RegisterAddress": "变量地址",
"OtherMethod": "特殊方法",
"Remark1": "备用1",
"Remark2": "备用2",
"Remark3": "备用3",
"Remark4": "备用4",
"Remark5": "备用5",
"AlarmCode": "报警值",
"RecoveryCode": "恢复值",
"AlarmLimit": "报警限值",
@@ -171,13 +146,9 @@
"AlarmEnable": "报警使能",
"Name": "名称",
"Name.Required": " {0} 不可为空",
"Description": "描述",
"DeviceId": "采集设备",
"DeviceId.MinValue": " {0} 不可为空",
"DeviceId.Required": " {0} 不可为空",
"IntervalTime": "执行间隔",
"IntervalTime.MinValue": " {0} 值太小",
"ArrayLength": "数组长度",
"Enable": "变量使能",
@@ -369,58 +340,20 @@
"NameDump": "设备名称 {0} 重复"
},
"ThingsGateway.Gateway.Application.ChannelRuntime": {
"Name": "名称",
"Name.Required": " {0} 不可为空",
"ChannelType": "通道类型",
"Enable": "启用",
"LogEnable": "启用调试日志",
"LogLevel": "日志等级",
"PluginName": "插件名称",
"PluginName.Required": "{0} 插件名称不可为空",
"RemoteUrl": "远程url",
"BindUrl": "本地url",
"PortName": "COM口",
"BaudRate": "波特率",
"DataBits": "数据位",
"Parity": "校验位",
"StopBits": "停止位",
"DtrEnable": "Dtr",
"RtsEnable": "Rts",
"CacheTimeout": "接收缓存超时",
"ConnectTimeout": "连接超时",
"MaxConcurrentCount": "最大并发数",
"MaxClientCount": "最大连接数",
"CheckClearTime": "客户端连接滑动过期时间",
"Heartbeat": "心跳内容(utf8)",
"HeartbeatTime": "心跳间隔",
"DtuId": "Dtu终端注册包(utf-8)",
"DtuSeviceType": "DTU服务类型",
"SaveChannel": "添加/修改通道",
"CopyChannel": "复制通道",
"DeleteChannel": "删除通道",
"ClearChannel": "清空通道",
"ExportChannel": "导出通道",
"ImportChannel": "导入通道",
"ImportNullError": "无法识别",
"NotOther": "不支持其他通道类型",
"Connect": "连接",
"Confim": "创建",
"Disconnect": "断开",
"Channel": "通道",
"NameDump": "通道名称 {0} 重复",
"DeviceRuntimeCounts": "设备数量"
"PluginType": "插件类型",
"MaxConcurrentCount": "最大并发数量",
"DeviceRuntimeCount": "设备数量"
},
"ThingsGateway.Gateway.Application.Channel": {
"SortCode": "排序",
"CreateTime": "创建时间",
"CreateUser": "创建人",
"UpdateTime": "更新时间",
"UpdateUser": "更新人",
"Name": "名称",
"Name.Required": " {0} 不可为空",
"ChannelType": "通道类型",
@@ -486,7 +419,8 @@
"Name": "名称",
"Name.Required": " {0} 不可为空",
"Description": "描述",
"Group": "组",
"Group": "业务组",
"CollectGroup": "采集组",
"DeviceId": "采集设备",
"DeviceId.MinValue": " {0} 不可为空",
"DeviceId.Required": " {0} 不可为空",

View File

@@ -103,7 +103,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
/// </summary>
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
public int? DeviceRuntimeCounts => DeviceRuntimes?.Count;
public int? DeviceRuntimeCount => DeviceRuntimes?.Count;
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
@@ -131,7 +131,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
public void Dispose()
{
Config?.SafeDispose();
//Config?.SafeDispose();
GlobalData.Channels.TryRemove(Id, out _);
DeviceThreadManage = null;
@@ -146,4 +146,57 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
return $"{Name}[{base.ToString()}]";
}
public IChannel GetChannel(TouchSocketConfig config)
{
lock (GlobalData.Channels)
{
if (DeviceThreadManage?.Channel?.DisposedValue == false)
return DeviceThreadManage?.Channel;
if (ChannelType == ChannelTypeEnum.TcpService
|| ChannelType == ChannelTypeEnum.SerialPort
|| ChannelType == ChannelTypeEnum.UdpSession
)
{
//获取相同配置的Tcp服务或Udp服务或COM
var same = GlobalData.Channels.FirstOrDefault(a =>
{
if (a.Value == this)
return false;
if (a.Value.DeviceThreadManage?.Channel?.DisposedValue == true || a.Value.DeviceThreadManage?.Channel?.DisposedValue == null)
return false;
if (a.Value.ChannelType == ChannelType)
{
if (a.Value.ChannelType == ChannelTypeEnum.TcpService)
if (a.Value.BindUrl == BindUrl)
return true;
if (a.Value.ChannelType == ChannelTypeEnum.UdpSession)
if ((!BindUrl.IsNullOrWhiteSpace()) && a.Value.BindUrl == BindUrl)
return true;
if (a.Value.ChannelType == ChannelTypeEnum.SerialPort)
if (a.Value.PortName == PortName)
return true;
}
return false;
}).Value;
if (same != null)
{
return same.GetChannel(config);
}
}
if (DeviceThreadManage?.Channel?.DisposedValue == false)
return DeviceThreadManage?.Channel;
var ichannel = config.GetChannel(this);
return ichannel;
}
}
}

View File

@@ -83,10 +83,15 @@ public class VariableBasicData : IPrimaryIdEntity
/// <inheritdoc cref="Variable.Name"/>
public string Name { get; set; }
/// <inheritdoc cref="Variable.Group"/>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string Group { get; set; }
/// <inheritdoc cref="VariableRuntime.DeviceName"/>
public string DeviceName { get; set; }
/// <inheritdoc cref="VariableRuntime.RuntimeType"/>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string RuntimeType { get; set; }
/// <inheritdoc cref="VariableRuntime.Value"/>
public object Value { get; set; }
/// <inheritdoc cref="VariableRuntime.RawValue"/>

View File

@@ -19,5 +19,8 @@ public class VariableMapper : IRegister
{
config.ForType<Variable, VariableRuntime>()
.Map(dest => dest.Value, src => src.InitValue);
config.ForType<VariableRuntime, VariableRuntime>()
.Ignore(dest => dest.DeviceRuntime);
}
}

View File

@@ -216,16 +216,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
await RuntimeServiceHelper.InitAsync(newChannelRuntimes, newDeviceRuntimes, _logger).ConfigureAwait(false);
var startCollectChannelEnable = GlobalData.StartCollectChannelEnable;
var startBusinessChannelEnable = GlobalData.StartBusinessChannelEnable;
var collectChannelRuntimes = newChannelRuntimes.Where(x => (x.Enable && x.IsCollect == true && startCollectChannelEnable));
var businessChannelRuntimes = newChannelRuntimes.Where(x => (x.Enable && x.IsCollect == false && startBusinessChannelEnable));
//根据初始冗余属性,筛选启动
await GlobalData.ChannelThreadManage.RestartChannelAsync(businessChannelRuntimes).ConfigureAwait(false);
await GlobalData.ChannelThreadManage.RestartChannelAsync(collectChannelRuntimes).ConfigureAwait(false);
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
}
catch (Exception ex)

View File

@@ -329,8 +329,8 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
ManageHelper.CheckChannelCount(insertData.Count);
using var db = GetDB();
await db.Fastest<Channel>().PageSize(100000).BulkCopyAsync(insertData).ConfigureAwait(false);
await db.Fastest<Channel>().PageSize(100000).BulkUpdateAsync(upData).ConfigureAwait(false);
await db.BulkCopyAsync(insertData, 100000).ConfigureAwait(false);
await db.BulkUpdateAsync(upData, 100000).ConfigureAwait(false);
DeleteChannelFromCache();
return channels.Select(a => a.Id).ToHashSet();
}

View File

@@ -351,8 +351,8 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
ManageHelper.CheckDeviceCount(insertData.Count);
using var db = GetDB();
await db.Fastest<Device>().PageSize(100000).BulkCopyAsync(insertData).ConfigureAwait(false);
await db.Fastest<Device>().PageSize(100000).BulkUpdateAsync(upData).ConfigureAwait(false);
await db.BulkCopyAsync(insertData, 100000).ConfigureAwait(false);
await db.BulkUpdateAsync(upData, 100000).ConfigureAwait(false);
DeleteDeviceFromCache();
return devices.Select(a => a.Id).ToHashSet();
}
@@ -379,9 +379,6 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
// 设备页的导入预览对象
ImportPreviewOutput<Device> deviceImportPreview = new();
// 获取所有驱动程序,并将驱动程序的完整名称作为键构建字典
var driverPluginFullNameDict = _pluginService.GetList().ToDictionary(a => a.FullName);
// 获取所有驱动程序,并将驱动程序名称作为键构建字典
var driverPluginNameDict = _pluginService.GetList().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();

View File

@@ -16,8 +16,6 @@ using System.Collections.Concurrent;
using ThingsGateway.NewLife;
using TouchSocket.Core;
namespace ThingsGateway.Gateway.Application;
internal sealed class ChannelThreadManage : IChannelThreadManage
@@ -115,8 +113,6 @@ internal sealed class ChannelThreadManage : IChannelThreadManage
{
await PrivateRemoveChannelsAsync(channelRuntimes.Select(a => a.Id)).ConfigureAwait(false);
BytePool.Default.Clear();
await channelRuntimes.ParallelForEachAsync(async (channelRuntime, token) =>
{
try

View File

@@ -117,10 +117,9 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
// 添加默认日志记录器
LogMessage.AddLogger(new EasyLogger(logger.Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
var ichannel = config.GetChannel(channelRuntime);
// 根据配置获取通道实例
Channel = ichannel;
Channel = channelRuntime.GetChannel(config);
//初始设置输出文本日志
SetLog(CurrentChannel.LogLevel);
@@ -901,7 +900,8 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
await NewDeviceLock.WaitAsync().ConfigureAwait(false);
await PrivateRemoveDevicesAsync(Drivers.Keys).ConfigureAwait(false);
Channel?.SafeDispose();
if (Channel?.Collects.Count == 0)
Channel?.SafeDispose();
LogMessage?.LogInformation(Localizer["ChannelDispose", CurrentChannel?.Name ?? string.Empty]);

View File

@@ -71,16 +71,7 @@ internal sealed class GatewayMonitorHostedService : BackgroundService, IGatewayM
}
}
var startCollectChannelEnable = GlobalData.StartCollectChannelEnable;
var startBusinessChannelEnable = GlobalData.StartBusinessChannelEnable;
var collectChannelRuntimes = channelRuntimes.Where(x => (x.Enable && x.IsCollect == true && startCollectChannelEnable));
var businessChannelRuntimes = channelRuntimes.Where(x => (x.Enable && x.IsCollect == false && startBusinessChannelEnable));
//根据初始冗余属性,筛选启动
await ChannelThreadManage.RestartChannelAsync(businessChannelRuntimes).ConfigureAwait(false);
await ChannelThreadManage.RestartChannelAsync(collectChannelRuntimes).ConfigureAwait(false);
await ChannelThreadManage.RestartChannelAsync(channelRuntimes).ConfigureAwait(false);
}

View File

@@ -310,7 +310,6 @@ internal static class RuntimeServiceHelper
}
}
item.Value.Dispose();
}
if (group.Key != null)
{
@@ -321,6 +320,19 @@ internal static class RuntimeServiceHelper
}
}
}
public static void VariableRuntimesDispose(IEnumerable<long> variableIds)
{
foreach (var variableId in variableIds)
{
if (GlobalData.IdVariables.TryGetValue(variableId, out var variableRuntime))
{
variableRuntime.Dispose();
}
}
}
public static void AddCollectChangedDriver(IEnumerable<VariableRuntime> newVariableRuntimes, ConcurrentHashSet<IDriver> changedDriver)

View File

@@ -43,10 +43,10 @@ public class VariableRuntimeService : IVariableRuntimeService
//获取变量,先找到原插件线程,然后修改插件线程内的字典,再改动全局字典,最后刷新插件
ConcurrentHashSet<IDriver> changedDriver = new();
RuntimeServiceHelper.VariableRuntimesDispose(variableIds);
RuntimeServiceHelper.AddCollectChangedDriver(newVariableRuntimes, changedDriver);
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
RuntimeServiceHelper.AddCollectChangedDriver(newVariableRuntimes, changedDriver);
if (restart)
{
@@ -79,9 +79,9 @@ public class VariableRuntimeService : IVariableRuntimeService
ConcurrentHashSet<IDriver> changedDriver = new();
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
RuntimeServiceHelper.VariableRuntimesDispose(variableIds);
RuntimeServiceHelper.AddCollectChangedDriver(newVariableRuntimes, changedDriver);
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
if (restart)
{
@@ -111,6 +111,7 @@ public class VariableRuntimeService : IVariableRuntimeService
ConcurrentHashSet<IDriver> changedDriver = new();
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
RuntimeServiceHelper.VariableRuntimesDispose(variableIds);
if (restart)
{
@@ -144,10 +145,9 @@ public class VariableRuntimeService : IVariableRuntimeService
var variableIds = newVariableRuntimes.Select(a => a.Id).ToHashSet();
ConcurrentHashSet<IDriver> changedDriver = new();
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
RuntimeServiceHelper.VariableRuntimesDispose(variableIds);
RuntimeServiceHelper.AddCollectChangedDriver(newVariableRuntimes, changedDriver);
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
if (restart)
{
@@ -237,10 +237,9 @@ public class VariableRuntimeService : IVariableRuntimeService
ConcurrentHashSet<IDriver> changedDriver = new();
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
RuntimeServiceHelper.VariableRuntimesDispose(variableIds);
RuntimeServiceHelper.AddCollectChangedDriver(newVariableRuntimes, changedDriver);
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
if (restart)
{

View File

@@ -217,9 +217,9 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
var result = await db.UseTranAsync(async () =>
{
await db.Fastest<Channel>().PageSize(100000).BulkCopyAsync(newChannels).ConfigureAwait(false);
await db.Fastest<Device>().PageSize(100000).BulkCopyAsync(newDevices).ConfigureAwait(false);
await db.Fastest<Variable>().PageSize(100000).BulkCopyAsync(newVariables).ConfigureAwait(false);
await db.BulkCopyAsync(newChannels, 100000).ConfigureAwait(false);
await db.BulkCopyAsync(newDevices, 100000).ConfigureAwait(false);
await db.BulkCopyAsync(newVariables, 100000).ConfigureAwait(false);
}).ConfigureAwait(false);
if (result.IsSuccess)//如果成功了
{
@@ -486,8 +486,8 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
var insertData = variables.Where(a => !a.IsUp).ToList();
ManageHelper.CheckVariableCount(insertData.Count);
using var db = GetDB();
await db.Fastest<Variable>().PageSize(100000).BulkCopyAsync(insertData).ConfigureAwait(false);
await db.Fastest<Variable>().PageSize(100000).BulkUpdateAsync(upData).ConfigureAwait(false);
await db.BulkCopyAsync(insertData, 100000).ConfigureAwait(false);
await db.BulkUpdateAsync(upData, 100000).ConfigureAwait(false);
_dispatchService.Dispatch(new());
DeleteVariableCache();
return variables.Select(a => a.Id).ToHashSet();
@@ -555,8 +555,6 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
// 设备页导入预览输出
ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview = new();
// 获取驱动插件的全名和名称的字典
var driverPluginFullNameDict = _pluginService.GetList().ToDictionary(a => a.FullName);
var driverPluginNameDict = _pluginService.GetList().ToDictionary(a => a.Name);
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();

View File

@@ -10,6 +10,12 @@
using Mapster;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using SqlSugar;
using System.Reflection;
namespace ThingsGateway.Gateway.Application;
@@ -17,14 +23,14 @@ namespace ThingsGateway.Gateway.Application;
[AppStartup(-100)]
public class Startup : AppStartup
{
public void ConfigureAdminApp(IServiceCollection services)
public void Configure(IServiceCollection services)
{
services.AddConfigurableOptions<ChannelThreadOptions>();
services.AddConfigurableOptions<GatewayLogOptions>();
services.AddConfigurableOptions<RpcLogOptions>();
//底层多语言配置
//Foundation.LocalizerUtil.SetLocalizerFactory((a) => App.CreateLocalizerByType(a));
Foundation.LocalizerUtil.SetLocalizerFactory((a) => App.CreateLocalizerByType(a));
TypeAdapterConfig.GlobalSettings.Scan(App.Assemblies.ToArray());
// 配置默认全局映射(支持覆盖)
@@ -62,8 +68,9 @@ public class Startup : AppStartup
services.AddGatewayHostedService<IGatewayMonitorHostedService, GatewayMonitorHostedService>();
}
public void UseAdminCore(IServiceProvider serviceProvider)
public void Use(IApplicationBuilder applicationBuilder)
{
var serviceProvider = applicationBuilder.ApplicationServices;
//检查ConfigId
var configIdGroup = DbContext.DbConfigs.GroupBy(it => it.ConfigId);
foreach (var configId in configIdGroup)
@@ -71,6 +78,7 @@ public class Startup : AppStartup
if (configId.Count() > 1) throw new($"Sqlsugar connect configId: {configId.Key} Duplicate!");
}
//遍历配置
DbContext.DbConfigs?.ForEach(it =>
{
@@ -79,7 +87,84 @@ public class Startup : AppStartup
if (it.InitTable == true)
connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建
});
//兼容变量名称唯一键处理
try
{
using var db = DbContext.GetDB<Variable>();
if (db.DbMaintenance.IsAnyIndex("unique_variable_name"))
{
DropIndex(db, "unique_variable_name", "variable");
}
}
catch { }
var fullName = Assembly.GetExecutingAssembly().FullName;//获取程序集全名
CodeFirstUtils.CodeFirst(fullName!);//CodeFirst
//10.4.9 删除logenable
try
{
using var db = DbContext.GetDB<Channel>();
if (db.DbMaintenance.IsAnyColumn(nameof(Channel), "LogEnable", false))
{
var tables = db.DbMaintenance.DropColumn(nameof(Channel), "LogEnable");
}
}
catch { }
try
{
using var db = DbContext.GetDB<Device>();
if (db.DbMaintenance.IsAnyColumn(nameof(Device), "LogEnable", false))
{
var tables = db.DbMaintenance.DropColumn(nameof(Device), "LogEnable");
}
}
catch { }
serviceProvider.GetService<IHostApplicationLifetime>().ApplicationStarted.Register(() =>
{
serviceProvider.GetService<ILoggerFactory>().CreateLogger(nameof(ThingsGateway)).LogInformation("ThingsGateway is started...");
});
serviceProvider.GetService<IHostApplicationLifetime>().ApplicationStopping.Register(() =>
{
serviceProvider.GetService<ILoggerFactory>().CreateLogger(nameof(ThingsGateway)).LogInformation("ThingsGateway is stopping...");
});
}
/// <summary>
/// 删除指定表上的索引(自动根据数据库类型生成正确的 DROP INDEX SQL
/// </summary>
/// <param name="db">数据库连接</param>
/// <param name="indexName">索引名</param>
/// <param name="tableName">表名(部分数据库需要)</param>
private static void DropIndex(SqlSugarClient db, string indexName, string tableName)
{
if (string.IsNullOrWhiteSpace(indexName))
throw new ArgumentNullException(nameof(indexName));
string dropIndexSql;
switch (db.CurrentConnectionConfig.DbType)
{
case SqlSugar.DbType.MySql:
case SqlSugar.DbType.SqlServer:
dropIndexSql = $"DROP INDEX {indexName} ON {tableName};";
break;
default:
dropIndexSql = $"DROP INDEX {indexName};";
break;
}
db.Ado.ExecuteCommand(dropIndexSql);
}
}

View File

@@ -7,10 +7,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="SqlSugar.TDengineCore" Version="4.18.6" />
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.0.25" />
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.0.25" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.2" />
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.1.2" />
<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" />
</ItemGroup>

View File

@@ -105,8 +105,8 @@ public class TcpSessionClientDto
public string PluginInfos { get; set; }
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
public DateTime LastReceivedTime { get; set; }
public DateTimeOffset LastReceivedTime { get; set; }
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
public DateTime LastSentTime { get; set; }
public DateTimeOffset LastSentTime { get; set; }
}

View File

@@ -99,7 +99,7 @@
</EditorItem>
<EditorItem Field=@context.DeviceRuntimeCounts FieldExpression=@(()=> context.DeviceRuntimeCounts ) />
<EditorItem Field=@context.DeviceRuntimeCount FieldExpression=@(()=> context.DeviceRuntimeCount ) />
</FieldItems>

View File

@@ -21,8 +21,6 @@
ShowExportButton
ShowDefaultButtons=true
ShowSearch=false
ShowExtendEditButton="true"
ShowExtendDeleteButton="true"
ExtendButtonColumnWidth=220
OnSaveAsync="Save"
OnDeleteAsync="Delete"

View File

@@ -1299,6 +1299,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
try
{
if (Disposed) return;
await Task.Delay(1000);
await OnClickSearch(SearchText);
Value = GetValue(Value);
@@ -1310,7 +1311,6 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
}
finally
{
await Task.Delay(2000);
_isExecuting = false;
}
}

View File

@@ -21,8 +21,6 @@
ShowExportButton
ShowDefaultButtons=true
ShowSearch=false
ShowExtendEditButton="true"
ShowExtendDeleteButton="true"
ExtendButtonColumnWidth=220
OnSaveAsync="Save"
OnDeleteAsync="Delete"

View File

@@ -129,7 +129,6 @@ public partial class PropertyComponent : IPropertyUIBase
return;
}
var op = new DialogOption()
{
IsScrolling = true,
@@ -144,6 +143,147 @@ public partial class PropertyComponent : IPropertyUIBase
{
{nameof(ScriptCheck.Data),data },
{nameof(ScriptCheck.Script),script },
{nameof(ScriptCheck.OnGetDemo),()=>
{
return
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel)?
"""
using ThingsGateway.Foundation;
using System.Dynamic;
using TouchSocket.Core;
public class S1 : IDynamicModel
{
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
{
List<ExpandoObject> deviceObjs = new List<ExpandoObject>();
foreach (var v in datas)
{
var device = (DeviceBasicData)v;
var expando = new ExpandoObject();
var deviceObj = new ExpandoObject();
deviceObj.TryAdd(nameof(Device.Description), device.Description);
deviceObj.TryAdd(nameof(DeviceBasicData.ActiveTime), device.ActiveTime);
deviceObj.TryAdd(nameof(DeviceBasicData.DeviceStatus), device.DeviceStatus.ToString());
deviceObj.TryAdd(nameof(DeviceBasicData.LastErrorMessage), device.LastErrorMessage);
deviceObj.TryAdd(nameof(DeviceBasicData.PluginName), device.PluginName);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark1), device.Remark1);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark2), device.Remark2);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark3), device.Remark3);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark4), device.Remark4);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark5), device.Remark5);
expando.TryAdd(nameof(Device.Name), deviceObj);
}
return deviceObjs;
}
}
"""
:
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptVariableModel)?
"""
using System.Dynamic;
using ThingsGateway.Foundation;
using TouchSocket.Core;
public class S2 : IDynamicModel
{
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
{
List<ExpandoObject> deviceObjs = new List<ExpandoObject>();
//按设备名称分组
var groups = datas.Where(a => !string.IsNullOrEmpty(((VariableBasicData)a).DeviceName)).GroupBy(a => ((VariableBasicData)a).DeviceName, a => ((VariableBasicData)a));
foreach (var group in groups)
{
//按采集时间分组
var data = group.GroupBy(a => a.CollectTime.DateTimeToUnixTimestamp());
var deviceObj = new ExpandoObject();
List<ExpandoObject> expandos = new List<ExpandoObject>();
foreach (var item in data)
{
var expando = new ExpandoObject();
expando.TryAdd("ts", item.Key);
var variableObj = new ExpandoObject();
foreach (var tag in item)
{
variableObj.TryAdd(tag.Name, tag.Value);
}
expando.TryAdd("values", variableObj);
expandos.Add(expando);
}
deviceObj.TryAdd(group.Key, expandos);
deviceObjs.Add(deviceObj);
}
return deviceObjs;
}
}
"""
:
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel)?
"""
using System.Dynamic;
using ThingsGateway.Foundation;
using TouchSocket.Core;
public class DeviceScript : IDynamicModel
{
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
{
List<ExpandoObject> deviceObjs = new List<ExpandoObject>();
//按设备名称分组
var groups = datas.Where(a => !string.IsNullOrEmpty(((AlarmVariable)a).DeviceName)).GroupBy(a => ((AlarmVariable)a).DeviceName, a => ((AlarmVariable)a));
foreach (var group in groups)
{
//按采集时间分组
var data = group.GroupBy(a => a.AlarmTime.DateTimeToUnixTimestamp());
var deviceObj = new ExpandoObject();
List<ExpandoObject> expandos = new List<ExpandoObject>();
foreach (var item in data)
{
var expando = new ExpandoObject();
expando.TryAdd("ts", item.Key);
var variableObj = new ExpandoObject();
foreach (var tag in item)
{
var alarmObj = new ExpandoObject();
alarmObj.TryAdd(nameof(tag.AlarmCode), tag.AlarmCode);
alarmObj.TryAdd(nameof(tag.AlarmText), tag.AlarmText);
alarmObj.TryAdd(nameof(tag.AlarmType), tag.AlarmType);
alarmObj.TryAdd(nameof(tag.AlarmLimit), tag.AlarmLimit);
alarmObj.TryAdd(nameof(tag.EventTime), tag.EventTime);
alarmObj.TryAdd(nameof(tag.EventType), tag.EventType);
variableObj.TryAdd(tag.Name, alarmObj);
}
expando.TryAdd("alarms", variableObj);
expandos.Add(expando);
}
deviceObj.TryAdd(group.Key, expandos);
deviceObjs.Add(deviceObj);
}
return deviceObjs;
}
}
"""
:
""
;
}
},
{nameof(ScriptCheck.ScriptChanged),EventCallback.Factory.Create<string>(this, v =>
{
if (pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel))
@@ -171,3 +311,7 @@ public partial class PropertyComponent : IPropertyUIBase
[Inject]
private DialogService DialogService { get; set; }
}

View File

@@ -18,6 +18,8 @@
<Alert Icon="fa-solid fa-circle-check" Color="Color.Success">@(new MarkupString("获取变量类实体,可用方法 <code>GlobalData.GetVariable(\"设备名称1\",\"变量名称1\")</code> "))</Alert>
<Alert Icon="fa-solid fa-circle-check" Color="Color.Success">@(new MarkupString("详细说明查看文档对应内容页面"))</Alert>
<Button IsAsync OnClick="GetDemo" class="mt-3" Text="Demo" />
</div>
<div class="col-6 col-md-6">
<BootstrapLabel Value=@Localizer["Input"] ShowLabelTooltip="true" />

View File

@@ -56,4 +56,13 @@ public partial class ScriptCheck
}
[Inject]
private IStringLocalizer<DeviceEditComponent> Localizer { get; set; }
private async Task GetDemo(Microsoft.AspNetCore.Components.Web.MouseEventArgs args)
{
Script = OnGetDemo?.Invoke();
await Change(Script);
}
[Parameter, EditorRequired]
public Func<string> OnGetDemo { get; set; }
}

View File

@@ -44,6 +44,7 @@
<EditorItem @bind-Field="@context.Name" Readonly=BatchEditEnable />
<EditorItem @bind-Field="@context.Description" />
<EditorItem @bind-Field="@context.CollectGroup" />
<EditorItem @bind-Field="@context.Group" />
<EditorItem @bind-Field="@context.Unit" />

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