Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6b0a9314c | ||
|
|
83bc07c3ff | ||
|
|
6ca680cd41 | ||
|
|
2b533dcb2b | ||
|
|
13a5a09f98 | ||
|
|
75ff7ebdbe | ||
|
|
6d8f3a1fc8 | ||
|
|
a11b411912 | ||
|
|
2dba817c69 | ||
|
|
d4df9a545f | ||
|
|
cae21391e5 | ||
|
|
573b22d2c2 | ||
|
|
14d4112a9d | ||
|
|
28aa9db9c5 | ||
|
|
d50c349e00 | ||
|
|
905362c59c | ||
|
|
97e7d44310 | ||
|
|
bb71a1ef23 | ||
|
|
de2771d008 | ||
|
|
02de34b65a | ||
|
|
c6e14889f2 | ||
|
|
f843732790 | ||
|
|
80e7ff0fc1 | ||
|
|
2964db6bdd | ||
|
|
5fbd94ec74 | ||
|
|
1d8d31d4f0 | ||
|
|
707c3eed99 | ||
|
|
e33a81ad36 | ||
|
|
0f7d216379 | ||
|
|
f0052e97a6 | ||
|
|
5c6076d48d | ||
|
|
883bd31249 | ||
|
|
d7b5fde3c7 | ||
|
|
f4273efef0 | ||
|
|
471dc966d9 | ||
|
|
50368654a4 | ||
|
|
b1e1cc04ce |
@@ -55,7 +55,7 @@ Password: **111111**
|
||||
## Docker
|
||||
```shell
|
||||
|
||||
docker pull diego2098/thingsgateway:latest
|
||||
docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
## Docker
|
||||
```shell
|
||||
|
||||
docker pull diego2098/thingsgateway:latest
|
||||
docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -37,27 +37,11 @@ import Highlight from '@site/src/components/Highlight.js';
|
||||
|执行间隔| 读取时间间隔/执行特殊方法读取的间隔 ||
|
||||
|特殊方法| 某些插件特有的方法 |对于不同的插件,特殊方法配置不相同,查看对应的插件说明|
|
||||
|数据类型| 对应的数据类型 |注意除了特殊驱动,一般不应该选择object|
|
||||
|读取表达式| 动态解析的表达式 ,原始值的代码为raw|示例:``raw*10+3`` 结果:原始值raw为8,输出实时值83 。具体可查看 [ExpressionEvaluator WiKi](https://github.com/codingseb/ExpressionEvaluator)|
|
||||
|写入表达式| 动态解析的表达式,在写入值时转化,原始值的代码为raw|示例:``raw/10`` 结果:写入值为230时,会下发23到PLC 。具体可查看 [ExpressionEvaluator WiKi](https://github.com/codingseb/ExpressionEvaluator)|
|
||||
|读取表达式| 动态解析的表达式 ,原始值的代码为raw|示例:``raw.ToInt()*10+3`` 结果:原始值raw为8,输出实时值83 。|
|
||||
|写入表达式| 动态解析的表达式,在写入值时转化,原始值的代码为raw|示例:``raw.ToInt()/10`` 结果:写入值为230时,会下发23到PLC 。|
|
||||
|备用字段| 存储自定义信息 |
|
||||
|
||||
:::tip `表达式特别说明`
|
||||
|
||||
网关还支持表达式的动态传入,除了raw表示该变量读取的原始值外,还支持其他变量的值传入表达式
|
||||
举例:
|
||||
```csharp
|
||||
|
||||
//新建testInt1,testInt2两个变量
|
||||
|
||||
//在testInt1的读取表达式中定义
|
||||
|
||||
raw*testInt2+3
|
||||
|
||||
//原始值raw为8,testInt2为100,输出实时值803
|
||||
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::tip `变量地址通用说明`
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import Highlight from '@site/src/components/Highlight.js';
|
||||
网关提供灵活的脚本式配置转换,可以在变量的读取表达式中进行配置转换
|
||||
如果要设置小数位为2,可以填入
|
||||
```
|
||||
Math.Round(raw, 2)
|
||||
Math.Round(raw.ToDecimal(), 2)
|
||||
```
|
||||
|
||||
#### 3、源码打开razor文件时,不出现智能提示,有waring警告(波浪线)
|
||||
|
||||
@@ -16,11 +16,12 @@ import Highlight from '@site/src/components/Highlight.js';
|
||||
//只传输变量名称,变量值,变量在线状态,变量值改变时间
|
||||
public class DemoScript:IDynamicModel
|
||||
{
|
||||
public IEnumerable<dynamic> GetList(IEnumerable<dynamic> datas)
|
||||
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
|
||||
{
|
||||
List<DemoData> demoDatas = new List<DemoData>();
|
||||
foreach (var data in datas)
|
||||
foreach (var v in datas)
|
||||
{
|
||||
var data=(VariableData)v;
|
||||
DemoData demoData = new DemoData();
|
||||
demoData.Value = data.Value;
|
||||
demoData.Name = data.Name;
|
||||
@@ -57,11 +58,11 @@ import Highlight from '@site/src/components/Highlight.js';
|
||||
|
||||
public class AliYunIotScript:IDynamicModel
|
||||
{
|
||||
public IEnumerable<dynamic> GetList(IEnumerable<dynamic> datas)
|
||||
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
|
||||
{
|
||||
List<AliYunIot> aliYunIots = new();
|
||||
// 对输入列表进行分组,根据 Remark1属性分组
|
||||
var groups = datas.Where(a => !string.IsNullOrEmpty(a.Remark1)).GroupBy(a => a.Remark1);
|
||||
var groups = datas.Where(a => !string.IsNullOrEmpty(((VariableData)a).Remark1)).GroupBy(a => ((VariableData)a).Remark1,a=> ((VariableData)a));
|
||||
// 遍历每一个分组
|
||||
foreach (var item in groups)
|
||||
{
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
|
||||
@@ -33,15 +31,11 @@ public class AuthController : ControllerBase
|
||||
return _authService.LoginAsync(input);
|
||||
}
|
||||
|
||||
[HttpGet("logout")]
|
||||
[HttpPost("logout")]
|
||||
[Authorize]
|
||||
[IgnoreRolePermission]
|
||||
public async Task<IActionResult> LogoutAsync([FromQuery] string returnUrl)
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
await _authService.LoginOutAsync();
|
||||
return Redirect(QueryHelpers.AddQueryString(Request.PathBase + CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
|
||||
{
|
||||
["ReturnUrl"] = returnUrl
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,8 @@
|
||||
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
|
||||
@@ -39,15 +37,12 @@ public class OpenApiController : ControllerBase
|
||||
return openApiLoginOutput;
|
||||
}
|
||||
|
||||
[HttpGet("logout")]
|
||||
[HttpPost("logout")]
|
||||
[Authorize]
|
||||
[IgnoreRolePermission]
|
||||
public async Task<IActionResult> LogoutAsync([FromQuery] string returnUrl)
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
await _authService.LoginOutAsync();
|
||||
return Redirect(QueryHelpers.AddQueryString(Request.PathBase + CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
|
||||
{
|
||||
["ReturnUrl"] = returnUrl
|
||||
}));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,13 @@ internal class AdminTaskService : BackgroundService
|
||||
{
|
||||
//实现 删除过期日志 功能,不需要精确的时间
|
||||
var daysAgo = App.Configuration.GetSection("LogJob:DaysAgo").Get<int?>() ?? 1;
|
||||
var verificatInfoCacheService = App.RootServices.GetService<IVerificatInfoCacheService>();
|
||||
var verificatInfoService = App.RootServices.GetService<IVerificatInfoService>();
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await DeleteSysOperateLog(daysAgo, stoppingToken).ConfigureAwait(false);
|
||||
verificatInfoCacheService.HashSetDB(verificatInfoCacheService.GetAll());
|
||||
await Task.Delay(TimeSpan.FromDays(1), stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -46,9 +45,6 @@ internal class AdminTaskService : BackgroundService
|
||||
_logger.LogWarning(ex, "Execute waining");
|
||||
}
|
||||
}
|
||||
|
||||
//实现 程序退出时,持久化TokenCache
|
||||
verificatInfoCacheService.HashSetDB(verificatInfoCacheService.GetAll());
|
||||
}
|
||||
|
||||
private async Task DeleteSysOperateLog(int daysAgo, CancellationToken stoppingToken)
|
||||
|
||||
@@ -189,7 +189,7 @@ public class APPInfo
|
||||
public string UpdateTime { get; set; }
|
||||
}
|
||||
|
||||
[SugarTable("tg_hardwareinfo", TableDescription = "硬件信息历史表")]
|
||||
[SugarTable("his_hardwareinfo", TableDescription = "硬件信息历史表")]
|
||||
[Tenant(SqlSugarConst.DB_HardwareInfo)]
|
||||
public class HisHardwareInfo
|
||||
{
|
||||
|
||||
@@ -30,11 +30,11 @@ public class AuthService : IAuthService
|
||||
private readonly ISysResourceService _sysResourceService;
|
||||
private readonly IUserCenterService _userCenterService;
|
||||
private IStringLocalizer<AuthService> _localizer;
|
||||
private IVerificatInfoCacheService _verificatInfoCacheService;
|
||||
private IVerificatInfoService _verificatInfoService;
|
||||
|
||||
public AuthService(ISysDictService configService, ISysResourceService sysResourceService,
|
||||
ISysUserService userService, IUserCenterService userCenterService,
|
||||
IVerificatInfoCacheService verificatInfoCacheService,
|
||||
IVerificatInfoService verificatInfoService,
|
||||
IStringLocalizer<AuthService> localizer)
|
||||
{
|
||||
_configService = configService;
|
||||
@@ -42,7 +42,7 @@ public class AuthService : IAuthService
|
||||
_sysResourceService = sysResourceService;
|
||||
_userCenterService = userCenterService;
|
||||
_localizer = localizer;
|
||||
_verificatInfoCacheService = verificatInfoCacheService;
|
||||
_verificatInfoService = verificatInfoService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -205,7 +205,8 @@ public class AuthService : IAuthService
|
||||
identity.AddClaim(new Claim(ClaimConst.Account, sysUser.Account));
|
||||
identity.AddClaim(new Claim(ClaimConst.SuperAdmin, sysUser.RoleCodeList.Contains(RoleConst.SuperAdmin).ToString()));
|
||||
|
||||
var diffTime = DateTime.Now.AddMinutes(expire);
|
||||
var diffTime = DateTime.MaxValue;
|
||||
//var diffTime = DateTime.Now.AddMinutes(expire);
|
||||
await App.HttpContext!.SignInAsync(nameof(ThingsGateway), new ClaimsPrincipal(identity), new AuthenticationProperties()
|
||||
{
|
||||
IsPersistent = true,
|
||||
@@ -260,8 +261,7 @@ public class AuthService : IAuthService
|
||||
App.CacheService.Remove(key);//移除登录错误次数
|
||||
|
||||
//获取用户verificat列表
|
||||
var tokenInfos = _verificatInfoCacheService.HashGetOne(sysUser.Id);
|
||||
var userToken = tokenInfos?.FirstOrDefault(it => it.Id == loginEvent.VerificatId);
|
||||
var userToken = _verificatInfoService.GetOne(loginEvent.VerificatId);
|
||||
|
||||
#endregion 登录/密码策略
|
||||
|
||||
@@ -294,7 +294,6 @@ public class AuthService : IAuthService
|
||||
private async Task WriteTokenToCache(LoginPolicy loginPolicy, LoginEvent loginEvent)
|
||||
{
|
||||
//获取verificat列表
|
||||
var verificatInfos = GetTokenInfos(loginEvent.SysUser.Id)?.ToList();
|
||||
var tokenTimeout = loginEvent.DateTime.AddMinutes(loginEvent.Expire);
|
||||
//生成verificat信息
|
||||
var verificatInfo = new VerificatInfo
|
||||
@@ -303,28 +302,16 @@ public class AuthService : IAuthService
|
||||
Expire = loginEvent.Expire,
|
||||
VerificatTimeout = tokenTimeout,
|
||||
Id = loginEvent.VerificatId,
|
||||
UserId = loginEvent.SysUser.Id
|
||||
};
|
||||
//如果cache有数据
|
||||
if (verificatInfos != null)
|
||||
//判断是否单用户登录
|
||||
if (loginPolicy.SingleOpen)
|
||||
{
|
||||
//判断是否单用户登录
|
||||
if (loginPolicy.SingleOpen)
|
||||
{
|
||||
await SingleLogin(loginEvent.SysUser.Id, verificatInfos);//单用户登录方法
|
||||
verificatInfos = new() { verificatInfo };
|
||||
}
|
||||
else
|
||||
{
|
||||
verificatInfos.Add(verificatInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
verificatInfos = new() { verificatInfo };
|
||||
await SingleLogin(loginEvent.SysUser.Id);//单用户登录方法
|
||||
}
|
||||
|
||||
//添加到verificat列表
|
||||
_verificatInfoCacheService.HashAdd(loginEvent.SysUser.Id, verificatInfos);
|
||||
_verificatInfoService.Add(verificatInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -333,53 +320,21 @@ public class AuthService : IAuthService
|
||||
/// <param name="loginEvent">登录事件参数</param>
|
||||
private void RemoveTokenFromCache(LoginEvent loginEvent)
|
||||
{
|
||||
//获取verificat列表
|
||||
var verificatInfos = GetTokenInfos(loginEvent.SysUser.Id).ToList();
|
||||
if (verificatInfos != null)
|
||||
{
|
||||
//获取当前用户的verificat
|
||||
var verificat = verificatInfos.FirstOrDefault(it => it.Id == loginEvent.VerificatId);
|
||||
if (verificat != null)
|
||||
verificatInfos.Remove(verificat);
|
||||
if (verificatInfos.Any())
|
||||
{
|
||||
//更新verificat列表
|
||||
_verificatInfoCacheService.HashAdd(loginEvent.SysUser.Id, verificatInfos);
|
||||
}
|
||||
else
|
||||
{
|
||||
//从列表中删除
|
||||
_verificatInfoCacheService.HashDel(loginEvent.SysUser.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户verificat列表
|
||||
/// </summary>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>verificat列表</returns>
|
||||
private IEnumerable<VerificatInfo> GetTokenInfos(long userId)
|
||||
{
|
||||
var verificatInfos = _verificatInfoCacheService.HashGetOne(userId);
|
||||
if (verificatInfos != null)
|
||||
{
|
||||
return verificatInfos.Where(it => it.VerificatTimeout > DateTime.Now);//去掉登录超时的
|
||||
}
|
||||
return verificatInfos;
|
||||
//更新verificat列表
|
||||
_verificatInfoService.Delete(loginEvent.VerificatId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单用户登录通知用户下线
|
||||
/// </summary>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <param name="verificatInfos">verificat列表</param>
|
||||
private async Task SingleLogin(long userId, IEnumerable<VerificatInfo> verificatInfos)
|
||||
private async Task SingleLogin(long userId)
|
||||
{
|
||||
var clientIds = _verificatInfoService.GetClientIdListByUserId(userId);
|
||||
await NoticeUtil.UserLoginOut(new UserLoginOutEvent
|
||||
{
|
||||
Message = _localizer["SingleLoginWarn"],
|
||||
VerificatInfos = verificatInfos,
|
||||
ClientIds = clientIds,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Mapster;
|
||||
|
||||
using NewLife.Extension;
|
||||
|
||||
using SqlSugar;
|
||||
@@ -20,11 +22,11 @@ namespace ThingsGateway.Admin.Application;
|
||||
|
||||
public class SessionService : BaseService<SysUser>, ISessionService
|
||||
{
|
||||
private readonly IVerificatInfoCacheService _verificatInfoCacheService;
|
||||
private readonly IVerificatInfoService _verificatInfoService;
|
||||
|
||||
public SessionService(IVerificatInfoCacheService verificatInfoCacheService)
|
||||
public SessionService(IVerificatInfoService verificatInfoService)
|
||||
{
|
||||
_verificatInfoCacheService = verificatInfoCacheService;
|
||||
_verificatInfoService = verificatInfoService;
|
||||
}
|
||||
|
||||
#region 查询
|
||||
@@ -35,10 +37,6 @@ public class SessionService : BaseService<SysUser>, ISessionService
|
||||
/// <param name="option">查询条件</param>
|
||||
public async Task<QueryData<SessionOutput>> PageAsync(QueryPageOptions option)
|
||||
{
|
||||
//获取verificat列表
|
||||
var bTokenInfoDic = GetTokenDicFromCache();
|
||||
//获取用户ID列表
|
||||
var userIds = bTokenInfoDic.Keys.Select(it => it.ToLong());
|
||||
var ret = new QueryData<SessionOutput>()
|
||||
{
|
||||
IsSorted = option.SortOrder != SortOrder.Unset,
|
||||
@@ -48,19 +46,8 @@ public class SessionService : BaseService<SysUser>, ISessionService
|
||||
};
|
||||
|
||||
using var db = GetDB();
|
||||
var query = db.GetQuery<SysUser>(option).WhereIF(!option.SearchText.IsNullOrWhiteSpace(), a => a.Account.Contains(option.SearchText!)).Select<SessionOutput>()
|
||||
.Mapper(it =>
|
||||
{
|
||||
if (bTokenInfoDic.TryGetValue(it.Id, out var verificatInfos))
|
||||
{
|
||||
GetTokenInfos(ref verificatInfos);//获取剩余时间
|
||||
it.VerificatCount = verificatInfos.Count;//令牌数量
|
||||
it.VerificatSignList = verificatInfos;//令牌列表
|
||||
var query = db.GetQuery<SysUser>(option).WhereIF(!option.SearchText.IsNullOrWhiteSpace(), a => a.Account.Contains(option.SearchText!));
|
||||
|
||||
//如果有mqtt客户端ID就是在线
|
||||
it.Online = verificatInfos.Any(it => it.ClientIds.Count > 0);
|
||||
}
|
||||
});
|
||||
if (option.IsPage)
|
||||
{
|
||||
RefAsync<int> totalCount = 0;
|
||||
@@ -68,8 +55,26 @@ public class SessionService : BaseService<SysUser>, ISessionService
|
||||
var items = await query
|
||||
.ToPageListAsync(option.PageIndex, option.PageItems, totalCount);
|
||||
|
||||
var verificatInfoDicts = _verificatInfoService.GetListByUserIds(items.Select(a => a.Id).ToList()).GroupBy(a => a.UserId).ToDictionary(a => a.Key, a => a.ToList());
|
||||
|
||||
var r = items.Select((it) =>
|
||||
{
|
||||
var reuslt = it.Adapt<SessionOutput>();
|
||||
if (verificatInfoDicts.TryGetValue(it.Id, out var verificatInfos))
|
||||
{
|
||||
GetTokenInfos(verificatInfos);//获取剩余时间
|
||||
reuslt.VerificatCount = verificatInfos.Count;//令牌数量
|
||||
reuslt.VerificatSignList = verificatInfos;//令牌列表
|
||||
|
||||
//如果有mqtt客户端ID就是在线
|
||||
reuslt.Online = verificatInfos.Any(it => it.ClientIds.Count > 0);
|
||||
}
|
||||
|
||||
return reuslt;
|
||||
}).ToList();
|
||||
|
||||
ret.TotalCount = totalCount;
|
||||
ret.Items = items;
|
||||
ret.Items = r;
|
||||
}
|
||||
else if (option.IsVirtualScroll)
|
||||
{
|
||||
@@ -77,16 +82,50 @@ public class SessionService : BaseService<SysUser>, ISessionService
|
||||
|
||||
var items = await query
|
||||
.ToPageListAsync(option.StartIndex, option.PageItems, totalCount);
|
||||
var verificatInfoDicts = _verificatInfoService.GetListByUserIds(items.Select(a => a.Id).ToList()).GroupBy(a => a.UserId).ToDictionary(a => a.Key, a => a.ToList());
|
||||
|
||||
var r = items.Select((it) =>
|
||||
{
|
||||
var reuslt = it.Adapt<SessionOutput>();
|
||||
if (verificatInfoDicts.TryGetValue(it.Id, out var verificatInfos))
|
||||
{
|
||||
GetTokenInfos(verificatInfos);//获取剩余时间
|
||||
reuslt.VerificatCount = verificatInfos.Count;//令牌数量
|
||||
reuslt.VerificatSignList = verificatInfos;//令牌列表
|
||||
|
||||
//如果有mqtt客户端ID就是在线
|
||||
reuslt.Online = verificatInfos.Any(it => it.ClientIds.Count > 0);
|
||||
}
|
||||
|
||||
return reuslt;
|
||||
}).ToList();
|
||||
ret.TotalCount = totalCount;
|
||||
ret.Items = items;
|
||||
ret.Items = r;
|
||||
}
|
||||
else
|
||||
{
|
||||
var items = await query
|
||||
.ToListAsync();
|
||||
|
||||
var verificatInfoDicts = _verificatInfoService.GetListByUserIds(items.Select(a => a.Id).ToList()).GroupBy(a => a.UserId).ToDictionary(a => a.Key, a => a.ToList());
|
||||
|
||||
var r = items.Select((it) =>
|
||||
{
|
||||
var reuslt = it.Adapt<SessionOutput>();
|
||||
if (verificatInfoDicts.TryGetValue(it.Id, out var verificatInfos))
|
||||
{
|
||||
GetTokenInfos(verificatInfos);//获取剩余时间
|
||||
reuslt.VerificatCount = verificatInfos.Count;//令牌数量
|
||||
reuslt.VerificatSignList = verificatInfos;//令牌列表
|
||||
|
||||
//如果有mqtt客户端ID就是在线
|
||||
reuslt.Online = verificatInfos.Any(it => it.ClientIds.Count > 0);
|
||||
}
|
||||
|
||||
return reuslt;
|
||||
}).ToList();
|
||||
ret.TotalCount = items.Count;
|
||||
ret.Items = items;
|
||||
ret.Items = r;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -102,11 +141,10 @@ public class SessionService : BaseService<SysUser>, ISessionService
|
||||
[OperDesc("ExitSession")]
|
||||
public async Task ExitSession(long userId)
|
||||
{
|
||||
var verificatInfoIds = _verificatInfoService.GetListByUserId(userId);
|
||||
//verificat列表
|
||||
var verificatInfos = _verificatInfoCacheService.HashGetOne(userId);
|
||||
//从列表中删除
|
||||
_verificatInfoCacheService.HashDel(userId);
|
||||
await NoticeUserLoginOut(userId, verificatInfos);
|
||||
_verificatInfoService.Delete(verificatInfoIds.Select(a => a.Id).ToList());
|
||||
await NoticeUserLoginOut(userId, verificatInfoIds.SelectMany(a => a.ClientIds).ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -118,67 +156,24 @@ public class SessionService : BaseService<SysUser>, ISessionService
|
||||
public async Task ExitVerificat(ExitVerificatInput input)
|
||||
{
|
||||
var userId = input.Id;
|
||||
//获取该用户的verificat信息
|
||||
var verificatInfos = _verificatInfoCacheService.HashGetOne(userId);
|
||||
//当前需要踢掉用户的verificat
|
||||
var deleteVerificats = verificatInfos.Where(it => input.VerificatIds.Contains(it.Id));
|
||||
//踢掉包含verificat列表的verificat信息
|
||||
verificatInfos = verificatInfos.Where(it => !input.VerificatIds.Contains(it.Id)).ToList();
|
||||
if (verificatInfos.Count > 0)
|
||||
_verificatInfoCacheService.HashAdd(userId, verificatInfos);//如果还有verificat则更新verificat
|
||||
else
|
||||
_verificatInfoCacheService.HashDel(userId);//否则直接删除key
|
||||
await NoticeUserLoginOut(userId, deleteVerificats);
|
||||
var data = input.VerificatIds.ToList();
|
||||
if (data.Any())
|
||||
{
|
||||
var data1 = _verificatInfoService.GetListByIds(data).SelectMany(a => a.ClientIds).ToList();
|
||||
_verificatInfoService.Delete(data);//如果还有verificat则更新verificat
|
||||
await NoticeUserLoginOut(userId, data1);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 修改
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取cache中verificat信息列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Dictionary<long, List<VerificatInfo>> GetTokenDicFromCache()
|
||||
{
|
||||
//cache获取verificat信息hash集合,并转成字典
|
||||
var bTokenDic = _verificatInfoCacheService.GetAll();
|
||||
if (bTokenDic != null)
|
||||
{
|
||||
foreach (var it in bTokenDic)
|
||||
{
|
||||
var verificats = it.Value.Where(it => it.VerificatTimeout.AddSeconds(30) > DateTime.Now).ToList();//去掉登录超时的
|
||||
if (verificats.Count == 0)
|
||||
{
|
||||
//表示都过期了
|
||||
bTokenDic.Remove(it.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
bTokenDic[it.Key] = verificats;//重新赋值verificat
|
||||
}
|
||||
}
|
||||
if (bTokenDic.Count > 0)
|
||||
{
|
||||
_verificatInfoCacheService.HashSet(bTokenDic);
|
||||
}
|
||||
else
|
||||
{
|
||||
_verificatInfoCacheService.Remove();
|
||||
}
|
||||
return bTokenDic;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Dictionary<long, List<VerificatInfo>>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取verificat剩余时间信息
|
||||
/// </summary>
|
||||
/// <param name="verificatInfos">verificat列表</param>
|
||||
private void GetTokenInfos(ref List<VerificatInfo> verificatInfos)
|
||||
private void GetTokenInfos(List<VerificatInfo> verificatInfos)
|
||||
{
|
||||
verificatInfos.ForEach(it =>
|
||||
{
|
||||
@@ -191,12 +186,12 @@ public class SessionService : BaseService<SysUser>, ISessionService
|
||||
/// 通知用户下线
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task NoticeUserLoginOut(long userId, IEnumerable<VerificatInfo> verificatInfos)
|
||||
private async Task NoticeUserLoginOut(long userId, List<long> clientIds)
|
||||
{
|
||||
await NoticeUtil.UserLoginOut(new UserLoginOutEvent
|
||||
{
|
||||
Message = Localizer["ExitVerificat"],
|
||||
VerificatInfos = verificatInfos,
|
||||
ClientIds = clientIds,
|
||||
});//通知用户下线
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ namespace ThingsGateway.Admin.Application;
|
||||
|
||||
public class SysHub : Hub<ISysHub>
|
||||
{
|
||||
private readonly IVerificatInfoCacheService _verificatInfoCacheService;
|
||||
private readonly IVerificatInfoService _verificatInfoService;
|
||||
|
||||
public SysHub(IVerificatInfoCacheService verificatInfoCacheService)
|
||||
public SysHub(IVerificatInfoService verificatInfoService)
|
||||
{
|
||||
_verificatInfoCacheService = verificatInfoCacheService;
|
||||
_verificatInfoService = verificatInfoService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,7 +45,9 @@ public class SysHub : Hub<ISysHub>
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
var userIdentifier = Context.UserIdentifier.ToLong();//自定义的Id
|
||||
UpdateVerificat(userIdentifier, isConnect: false);//更新cache
|
||||
var feature = Context.Features.Get<IHttpContextFeature>();
|
||||
var VerificatId = feature!.HttpContext!.Request.Headers[ClaimConst.VerificatId].FirstOrDefault().ToLong();
|
||||
UpdateVerificat(userIdentifier, verificat: VerificatId, isConnect: false);//更新cache
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
|
||||
@@ -62,32 +64,28 @@ public class SysHub : Hub<ISysHub>
|
||||
if (userId != 0)
|
||||
{
|
||||
//获取cache当前用户的verificat信息列表
|
||||
var verificatInfos = _verificatInfoCacheService.HashGetOne(userId);
|
||||
if (verificatInfos != null)
|
||||
if (isConnect)
|
||||
{
|
||||
if (isConnect)
|
||||
//获取cache中当前verificat
|
||||
var verificatInfo = _verificatInfoService.GetOne(verificat);
|
||||
if (verificatInfo != null)
|
||||
{
|
||||
//获取cache中当前verificat
|
||||
var verificatInfo = verificatInfos.FirstOrDefault(it => it.Id == verificat);
|
||||
if (verificatInfo != null)
|
||||
{
|
||||
verificatInfo.ClientIds.Add(userId);//添加到客户端列表
|
||||
_verificatInfoCacheService.HashAdd(userId, verificatInfos);//更新Cache
|
||||
}
|
||||
verificatInfo.ClientIds.Add(userId);//添加到客户端列表
|
||||
_verificatInfoService.Update(verificatInfo);//更新Cache
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
//获取当前客户端ID所在的verificat信息
|
||||
var verificatInfo = _verificatInfoService.GetOne(verificat);
|
||||
if (verificatInfo != null)
|
||||
{
|
||||
//获取当前客户端ID所在的verificat信息
|
||||
var verificatInfo = verificatInfos.FirstOrDefault(it => it.ClientIds.Contains(userId));
|
||||
if (verificatInfo != null)
|
||||
{
|
||||
verificatInfo.ClientIds.RemoveWhere(it => it == userId);//从客户端列表删除
|
||||
_verificatInfoCacheService.HashAdd(userId, verificatInfos);//更新Cache
|
||||
}
|
||||
verificatInfo.ClientIds.RemoveWhere(it => it == userId);//从客户端列表删除
|
||||
_verificatInfoService.Update(verificatInfo);//更新Cache
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
#endregion 方法
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,10 @@ public class SysUserService : BaseService<SysUser>, ISysUserService
|
||||
private readonly ISysResourceService _sysResourceService;
|
||||
private readonly ISysRoleService _roleService;
|
||||
private readonly ISysDictService _configService;
|
||||
private readonly IVerificatInfoCacheService _verificatInfoCacheService;
|
||||
private readonly IVerificatInfoService _verificatInfoService;
|
||||
|
||||
public SysUserService(
|
||||
IVerificatInfoCacheService verificatInfoCacheService,
|
||||
IVerificatInfoService verificatInfoService,
|
||||
IRelationService relationService,
|
||||
ISysResourceService sysResourceService,
|
||||
ISysRoleService roleService,
|
||||
@@ -39,7 +39,7 @@ public class SysUserService : BaseService<SysUser>, ISysUserService
|
||||
_sysResourceService = sysResourceService;
|
||||
_roleService = roleService;
|
||||
_configService = configService;
|
||||
_verificatInfoCacheService = verificatInfoCacheService;
|
||||
_verificatInfoService = verificatInfoService;
|
||||
}
|
||||
|
||||
#region 查询
|
||||
@@ -331,10 +331,12 @@ public class SysUserService : BaseService<SysUser>, ISysUserService
|
||||
{
|
||||
DeleteUserFromCache(sysUser.Id);//删除用户缓存
|
||||
|
||||
var verificatInfos = _verificatInfoCacheService.HashGetOne(sysUser.Id);
|
||||
var verificatInfoIds = _verificatInfoService.GetListByUserId(sysUser.Id);
|
||||
|
||||
//从列表中删除
|
||||
//删除用户verificat缓存
|
||||
_verificatInfoCacheService.HashDel(sysUser.Id);
|
||||
await NoticeUtil.UserLoginOut(new UserLoginOutEvent() { VerificatInfos = verificatInfos, Message = Localizer["ExitVerificat"] });
|
||||
_verificatInfoService.Delete(verificatInfoIds.Select(a => a.Id).ToList());
|
||||
await NoticeUtil.UserLoginOut(new UserLoginOutEvent() { ClientIds = verificatInfoIds.SelectMany(a => a.ClientIds).ToList(), Message = Localizer["ExitVerificat"] });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -359,10 +361,10 @@ public class SysUserService : BaseService<SysUser>, ISysUserService
|
||||
}, it => it.Id == id))
|
||||
{
|
||||
DeleteUserFromCache(id);//从cache删除用户信息
|
||||
var verificatInfos = _verificatInfoCacheService.HashGetOne(id);
|
||||
var verificatInfoIds = _verificatInfoService.GetListByUserId(id);
|
||||
//删除用户verificat缓存
|
||||
_verificatInfoCacheService.HashDel(id);
|
||||
await NoticeUtil.UserLoginOut(new UserLoginOutEvent() { VerificatInfos = verificatInfos, Message = Localizer["ExitVerificat"] });
|
||||
_verificatInfoService.Delete(verificatInfoIds.Select(a => a.Id).ToList());
|
||||
await NoticeUtil.UserLoginOut(new UserLoginOutEvent() { ClientIds = verificatInfoIds.SelectMany(a => a.ClientIds).ToList(), Message = Localizer["ExitVerificat"] });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,13 +525,12 @@ public class SysUserService : BaseService<SysUser>, ISysUserService
|
||||
_relationService.RefreshCache(RelationCategoryEnum.UserHasPermission);//关系表刷新UserHasRole缓存
|
||||
_relationService.RefreshCache(RelationCategoryEnum.UserHasOpenApiPermission);//关系表刷新Relation_SYS_USER_HAS_OPENAPIPERMISSION缓存
|
||||
//将这些用户踢下线,并永久注销这些用户
|
||||
foreach (var item in ids)
|
||||
foreach (var id in ids)
|
||||
{
|
||||
var verificatInfos = _verificatInfoCacheService.HashGetOne(item);
|
||||
await UserLoginOut(item, verificatInfos);
|
||||
var verificatInfoIds = _verificatInfoService.GetListByUserId(id);
|
||||
_verificatInfoService.Delete(verificatInfoIds.Select(a => a.Id).ToList());
|
||||
await UserLoginOut(id, verificatInfoIds.SelectMany(a => a.ClientIds).ToList());
|
||||
}
|
||||
//从列表中删除
|
||||
_verificatInfoCacheService.HashDel(ids.ToArray());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -572,13 +573,13 @@ public class SysUserService : BaseService<SysUser>, ISysUserService
|
||||
/// 通知用户下线
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="verificatInfos">Token列表</param>
|
||||
private async Task UserLoginOut(long userId, IEnumerable<VerificatInfo>? verificatInfos)
|
||||
/// <param name="verificatInfoIds">Token列表</param>
|
||||
private async Task UserLoginOut(long userId, List<long>? verificatInfoIds)
|
||||
{
|
||||
await NoticeUtil.UserLoginOut(new UserLoginOutEvent
|
||||
{
|
||||
Message = Localizer["SingleLoginWarn"],
|
||||
VerificatInfos = verificatInfos,
|
||||
ClientIds = verificatInfoIds,
|
||||
});//通知用户下线
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@ public class UserCenterService : BaseService<SysUser>, IUserCenterService
|
||||
private readonly IRelationService _relationService;
|
||||
private readonly ISysResourceService _sysResourceService;
|
||||
private readonly ISysDictService _configService;
|
||||
private readonly IVerificatInfoCacheService _verificatInfoCacheService;
|
||||
private readonly IVerificatInfoService _verificatInfoService;
|
||||
|
||||
public UserCenterService(ISysUserService userService,
|
||||
IRelationService relationService,
|
||||
IVerificatInfoCacheService verificatInfoCacheService,
|
||||
IVerificatInfoService verificatInfoService,
|
||||
|
||||
ISysResourceService sysResourceService,
|
||||
ISysDictService configService)
|
||||
@@ -39,7 +39,7 @@ public class UserCenterService : BaseService<SysUser>, IUserCenterService
|
||||
_relationService = relationService;
|
||||
_sysResourceService = sysResourceService;
|
||||
_configService = configService;
|
||||
_verificatInfoCacheService = verificatInfoCacheService;
|
||||
_verificatInfoService = verificatInfoService;
|
||||
}
|
||||
|
||||
#region 查询
|
||||
@@ -233,10 +233,10 @@ public class UserCenterService : BaseService<SysUser>, IUserCenterService
|
||||
_userService.DeleteUserFromCache(UserManager.UserId);//cache删除用户数据
|
||||
|
||||
//将这些用户踢下线,并永久注销这些用户
|
||||
var verificatInfos = _verificatInfoCacheService.HashGetOne(UserManager.UserId);
|
||||
await UserLoginOut(UserManager.UserId, verificatInfos);
|
||||
var verificatInfoIds = _verificatInfoService.GetListByUserId(UserManager.UserId);
|
||||
//从列表中删除
|
||||
_verificatInfoCacheService.HashDel(UserManager.UserId);
|
||||
_verificatInfoService.Delete(verificatInfoIds.Select(a => a.Id).ToList());
|
||||
await UserLoginOut(UserManager.UserId, verificatInfoIds.SelectMany(a => a.ClientIds).ToList());
|
||||
}
|
||||
|
||||
#endregion 编辑
|
||||
@@ -247,13 +247,13 @@ public class UserCenterService : BaseService<SysUser>, IUserCenterService
|
||||
/// 通知用户下线
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="verificatInfos">Token列表</param>
|
||||
private async Task UserLoginOut(long userId, IEnumerable<VerificatInfo> verificatInfos)
|
||||
/// <param name="verificatInfoIds">Token列表</param>
|
||||
private async Task UserLoginOut(long userId, List<long> clientIds)
|
||||
{
|
||||
await NoticeUtil.UserLoginOut(new UserLoginOutEvent
|
||||
{
|
||||
Message = Localizer["PasswordEdited"],
|
||||
VerificatInfos = verificatInfos,
|
||||
ClientIds = clientIds,
|
||||
});//通知用户下线
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
// Website: https://www.blazor.zone or https://argozhang.github.io/
|
||||
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
|
||||
public interface IVerificatInfoService
|
||||
{
|
||||
void Add(VerificatInfo verificatInfo);
|
||||
|
||||
void Delete(long Id);
|
||||
|
||||
List<VerificatInfo>? GetListByUserId(long userId);
|
||||
|
||||
VerificatInfo GetOne(long id);
|
||||
|
||||
void RemoveAllClientId();
|
||||
|
||||
void Update(VerificatInfo verificatInfo);
|
||||
|
||||
void Delete(List<long> ids);
|
||||
|
||||
List<long>? GetIdListByUserId(long userId);
|
||||
|
||||
List<VerificatInfo>? GetListByUserIds(List<long> userIds);
|
||||
|
||||
List<long>? GetClientIdListByUserId(long userId);
|
||||
|
||||
List<VerificatInfo>? GetListByIds(List<long> ids);
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using SqlSugar;
|
||||
|
||||
using ThingsGateway.Admin.Application.ConcurrentList;
|
||||
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 操作内存,只在程序停止/启动时设置/获取持久化数据
|
||||
/// </summary>
|
||||
public class VerificatInfoService : BaseService<VerificatInfo>, IVerificatInfoService
|
||||
{
|
||||
#region 查询
|
||||
|
||||
public VerificatInfo GetOne(long id)
|
||||
{
|
||||
//先从Cache拿
|
||||
var verificatInfo = App.CacheService.HashGetOne<VerificatInfo>(CacheConst.Cache_Token, id.ToString());
|
||||
verificatInfo ??= GetFromDb(id);
|
||||
if (verificatInfo != null)
|
||||
if (verificatInfo.VerificatTimeout.AddSeconds(30) < DateTime.Now)
|
||||
{
|
||||
Delete(verificatInfo.Id);
|
||||
return null;
|
||||
}
|
||||
return verificatInfo;
|
||||
}
|
||||
|
||||
private VerificatInfo? GetFromDb(long id)
|
||||
{
|
||||
using var db = GetDB();
|
||||
var verificatInfo = db.Queryable<VerificatInfo>().First(u => u.Id == id);
|
||||
if (verificatInfo != null)
|
||||
SetCahce(verificatInfo);
|
||||
return verificatInfo;
|
||||
}
|
||||
|
||||
private void SetCahce(VerificatInfo verificatInfo)
|
||||
{
|
||||
App.CacheService.HashAdd<VerificatInfo>(CacheConst.Cache_Token, verificatInfo.Id.ToString(), verificatInfo);
|
||||
}
|
||||
|
||||
public List<VerificatInfo>? GetListByUserId(long userId)
|
||||
{
|
||||
using var db = GetDB();
|
||||
var verificatInfo = db.Queryable<VerificatInfo>().Where(u => u.UserId == userId).ToList();
|
||||
return verificatInfo;
|
||||
}
|
||||
|
||||
public List<VerificatInfo>? GetListByIds(List<long> ids)
|
||||
{
|
||||
using var db = GetDB();
|
||||
var verificatInfos = db.Queryable<VerificatInfo>().Where(u => ids.Contains(u.Id)).ToList();
|
||||
var ids1 = new List<long>();
|
||||
foreach (var verificatInfo in verificatInfos)
|
||||
{
|
||||
if (verificatInfo.VerificatTimeout.AddSeconds(30) < DateTime.Now)
|
||||
{
|
||||
ids1.Add(verificatInfo.Id);
|
||||
}
|
||||
}
|
||||
|
||||
if (ids1.Count > 0)
|
||||
{
|
||||
Delete(ids1);
|
||||
}
|
||||
return verificatInfos;
|
||||
}
|
||||
|
||||
public List<VerificatInfo>? GetListByUserIds(List<long> userIds)
|
||||
{
|
||||
using var db = GetDB();
|
||||
var verificatInfos = db.Queryable<VerificatInfo>().Where(u => userIds.Contains(u.UserId)).ToList();
|
||||
|
||||
List<long> ids = new List<long>();
|
||||
foreach (var verificatInfo in verificatInfos)
|
||||
{
|
||||
if (verificatInfo.VerificatTimeout.AddSeconds(30) < DateTime.Now)
|
||||
{
|
||||
ids.Add(verificatInfo.Id);
|
||||
}
|
||||
}
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
Delete(ids);
|
||||
}
|
||||
return verificatInfos;
|
||||
}
|
||||
|
||||
public List<long>? GetIdListByUserId(long userId)
|
||||
{
|
||||
using var db = GetDB();
|
||||
var verificatInfo = db.Queryable<VerificatInfo>().Where(u => u.UserId == userId).Select(a => a.Id).ToList();
|
||||
|
||||
return verificatInfo;
|
||||
}
|
||||
|
||||
public List<long>? GetClientIdListByUserId(long userId)
|
||||
{
|
||||
using var db = GetDB();
|
||||
var verificatInfo = db.Queryable<VerificatInfo>().Where(u => u.UserId == userId).Select(a => a.ClientIds).ToList().SelectMany(a => a).ToList();
|
||||
|
||||
return verificatInfo;
|
||||
}
|
||||
|
||||
#endregion 查询
|
||||
|
||||
#region 添加
|
||||
|
||||
public void Add(VerificatInfo verificatInfo)
|
||||
{
|
||||
using var db = GetDB();
|
||||
db.Insertable<VerificatInfo>(verificatInfo).ExecuteCommand();
|
||||
RemoveCache(verificatInfo.Id);
|
||||
if (verificatInfo != null)
|
||||
SetCahce(verificatInfo);
|
||||
}
|
||||
|
||||
#endregion 添加
|
||||
|
||||
#region 更新
|
||||
|
||||
public void Update(VerificatInfo verificatInfo)
|
||||
{
|
||||
using var db = GetDB();
|
||||
db.Updateable<VerificatInfo>(verificatInfo).ExecuteCommand();
|
||||
RemoveCache(verificatInfo.Id);
|
||||
if (verificatInfo != null)
|
||||
SetCahce(verificatInfo);
|
||||
}
|
||||
|
||||
#endregion 更新
|
||||
|
||||
#region 删除
|
||||
|
||||
public void Delete(long id)
|
||||
{
|
||||
using var db = GetDB();
|
||||
db.Deleteable<VerificatInfo>(id).ExecuteCommand();
|
||||
RemoveCache(id);
|
||||
}
|
||||
|
||||
public void Delete(List<long> ids)
|
||||
{
|
||||
using var db = GetDB();
|
||||
db.Deleteable<VerificatInfo>().Where(it => ids.Contains(it.Id)).ExecuteCommand();
|
||||
foreach (var id in ids)
|
||||
{
|
||||
RemoveCache(id);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 删除
|
||||
|
||||
#region 去除全部在线Id
|
||||
|
||||
public void RemoveAllClientId()
|
||||
{
|
||||
using var db = GetDB();
|
||||
db.Updateable<VerificatInfo>().SetColumns(it => it.ClientIds == default).Where(a => a.Id >= 0).ExecuteCommand();
|
||||
RemoveCache();
|
||||
}
|
||||
|
||||
#endregion 去除全部在线Id
|
||||
|
||||
private void RemoveCache()
|
||||
{
|
||||
App.CacheService.Remove(CacheConst.Cache_Token);
|
||||
}
|
||||
|
||||
private void RemoveCache(long id)
|
||||
{
|
||||
App.CacheService.HashDel<VerificatInfo>(CacheConst.Cache_Token, id.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 会话信息
|
||||
/// </summary>
|
||||
|
||||
[SugarTable("verificatinfo", TableDescription = "验证缓存表")]
|
||||
[Tenant(SqlSugarConst.DB_TokenCache)]
|
||||
public class VerificatInfo : PrimaryIdEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户端ID列表
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
[SugarColumn(ColumnDescription = "客户端ID列表", IsNullable = true, IsJson = true)]
|
||||
public ConcurrentList<long> ClientIds { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 验证Id
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 验证Id
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true)]
|
||||
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
|
||||
[IgnoreExcel]
|
||||
public override long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 在线状态
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true)]
|
||||
[SqlSugar.SugarColumn(IsIgnore = true)]
|
||||
public bool Online => ClientIds.Any();
|
||||
|
||||
/// <summary>
|
||||
/// 过期时间
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true)]
|
||||
public int Expire { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// verificat剩余有效期
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true)]
|
||||
[SqlSugar.SugarColumn(IsIgnore = true)]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string VerificatRemain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 超时时间
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true)]
|
||||
public DateTime VerificatTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 登录设备
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true, Width = 100)]
|
||||
public AuthDeviceTypeEnum Device { get; set; }
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
|
||||
public interface IVerificatInfoCacheService
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据id获取单个VerificatInfo列表
|
||||
/// </summary>
|
||||
/// <param name="id">要查询的id</param>
|
||||
/// <returns>VerificatInfo列表</returns>
|
||||
List<VerificatInfo> HashGetOne(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 根据多个id获取VerificatInfo列表集合
|
||||
/// </summary>
|
||||
/// <param name="ids">要查询的id数组</param>
|
||||
/// <returns>VerificatInfo列表集合</returns>
|
||||
IEnumerable<List<VerificatInfo>> HashGet(long[] ids);
|
||||
|
||||
/// <summary>
|
||||
/// 添加或更新指定id的VerificatInfo列表
|
||||
/// </summary>
|
||||
/// <param name="id">要添加或更新的id</param>
|
||||
/// <param name="verificatInfos">要添加或更新的VerificatInfo列表</param>
|
||||
void HashAdd(long id, List<VerificatInfo> verificatInfos);
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有VerificatInfo数据
|
||||
/// </summary>
|
||||
Dictionary<long, List<VerificatInfo>> GetAll();
|
||||
|
||||
/// <summary>
|
||||
/// 设置整个VerificatInfo缓存数据
|
||||
/// </summary>
|
||||
/// <param name="dictionary">以id为键,VerificatInfo列表为值的字典</param>
|
||||
void HashSet(Dictionary<long, List<VerificatInfo>> dictionary);
|
||||
|
||||
/// <summary>
|
||||
/// 删除所有VerificatInfo缓存数据
|
||||
/// </summary>
|
||||
void Remove();
|
||||
|
||||
/// <summary>
|
||||
/// 根据id删除对应的VerificatInfo数据
|
||||
/// </summary>
|
||||
/// <param name="ids">要删除的id数组</param>
|
||||
void HashDel(params long[] ids);
|
||||
|
||||
/// <summary>
|
||||
/// 持久化数据
|
||||
/// </summary>
|
||||
/// <param name="dictionary">数据</param>
|
||||
void HashSetDB(Dictionary<long, List<VerificatInfo>> dictionary);
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using SqlSugar;
|
||||
|
||||
using ThingsGateway.Admin.Application.ConcurrentList;
|
||||
using ThingsGateway.Core.Extension;
|
||||
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 操作内存,只在程序停止/启动时设置/获取持久化数据
|
||||
/// </summary>
|
||||
public class VerificatInfoCacheService : BaseService<VerificatInfoCacheItem>, IVerificatInfoCacheService
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public List<VerificatInfo> HashGetOne(long id)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var all = GetAll();
|
||||
var data = all.FirstOrDefault(a => a.Key == id);
|
||||
return data.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<List<VerificatInfo>> HashGet(long[] ids)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var data = GetAll();
|
||||
var results = data.Where(a => ids.Contains(a.Key)).Select(a => a.Value);
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void HashAdd(long id, List<VerificatInfo> verificatInfos)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var data = GetAll();
|
||||
if (data.ContainsKey(id))
|
||||
{
|
||||
data[id] = verificatInfos;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.TryAdd(id, verificatInfos);
|
||||
}
|
||||
if (App.IsDevelopment)
|
||||
HashSetDB(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void HashSet(Dictionary<long, List<VerificatInfo>> dict)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var key = CacheConst.Cache_Token;
|
||||
App.CacheService.Set(key, dict);
|
||||
if (App.IsDevelopment)
|
||||
HashSetDB(dict);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Remove()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var key = CacheConst.Cache_Token;
|
||||
App.CacheService.Set(key, new Dictionary<long, List<VerificatInfo>>());
|
||||
if (App.IsDevelopment)
|
||||
HashSetDB(new Dictionary<long, List<VerificatInfo>>());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void HashDel(params long[] ids)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var data = GetAll();
|
||||
data.RemoveWhere(a => ids.Contains(a.Key));
|
||||
if (App.IsDevelopment)
|
||||
if (ids.Length > 0)
|
||||
HashSetDB(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存/数据库获取全部信息
|
||||
/// </summary>
|
||||
/// <returns>列表</returns>
|
||||
public Dictionary<long, List<VerificatInfo>> GetAll()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var key = CacheConst.Cache_Token;
|
||||
var dict = App.CacheService.Get<Dictionary<long, List<VerificatInfo>>>(key);
|
||||
if (dict == null)
|
||||
{
|
||||
using var db = GetDB();
|
||||
dict = db.Queryable<VerificatInfoCacheItem>()?.ToList()?.ToDictionary(a => a.Id, a => a.VerificatInfos);
|
||||
App.CacheService.Set(key, dict);
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 持久化数据
|
||||
/// </summary>
|
||||
/// <param name="dictionary"></param>
|
||||
public void HashSetDB(Dictionary<long, List<VerificatInfo>> dictionary)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
using var db = GetDB();
|
||||
var count = db.Storageable(dictionary.Select(a => new VerificatInfoCacheItem() { Id = a.Key, VerificatInfos = a.Value }).ToList()).ExecuteCommand();
|
||||
if (count > 0)
|
||||
RemoveCache();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveCache()
|
||||
{
|
||||
App.CacheService.Remove(CacheConst.Cache_Token);
|
||||
}
|
||||
}
|
||||
|
||||
[SugarTable("tg_verificat", TableDescription = "验证缓存表")]
|
||||
[Tenant(SqlSugarConst.DB_TokenCache)]
|
||||
public class VerificatInfoCacheItem : PrimaryIdEntity
|
||||
{
|
||||
[SugarColumn(ColumnDescription = "验证列表", IsNullable = false, IsJson = true)]
|
||||
public List<VerificatInfo> VerificatInfos { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 会话信息
|
||||
/// </summary>
|
||||
|
||||
public class VerificatInfo : PrimaryIdEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户端ID列表
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public ConcurrentList<long> ClientIds { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 验证Id
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true)]
|
||||
public override long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 在线状态
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true)]
|
||||
[SqlSugar.SugarColumn(IsIgnore = true)]
|
||||
public bool Online => ClientIds.Any();
|
||||
|
||||
/// <summary>
|
||||
/// 过期时间
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true)]
|
||||
public int Expire { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// verificat剩余有效期
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true)]
|
||||
[SqlSugar.SugarColumn(IsIgnore = true)]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string VerificatRemain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 超时时间
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true)]
|
||||
public DateTime VerificatTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 登录设备
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true, Width = 100)]
|
||||
public AuthDeviceTypeEnum Device { get; set; }
|
||||
}
|
||||
@@ -163,7 +163,7 @@ public class Startup : AppStartup
|
||||
|
||||
services.AddSingleton<IFileService, FileService>();
|
||||
services.AddSingleton<IImportExportService, ImportExportService>();
|
||||
services.AddSingleton<IVerificatInfoCacheService, VerificatInfoCacheService>();
|
||||
services.AddSingleton<IVerificatInfoService, VerificatInfoService>();
|
||||
services.AddSingleton<IAuthService, AuthService>();
|
||||
services.AddSingleton<ISysDictService, SysDictService>();
|
||||
services.AddSingleton<ISysOperateLogService, SysOperateLogService>();
|
||||
@@ -183,16 +183,7 @@ public class Startup : AppStartup
|
||||
public void UseAdminCore(IApplicationBuilder app)
|
||||
{
|
||||
//删除在线用户统计
|
||||
var verificatInfoCacheService = app.ApplicationServices.GetService<IVerificatInfoCacheService>();
|
||||
var verificatInfos = verificatInfoCacheService.GetAll();
|
||||
//获取当前客户端ID所在的verificat信息
|
||||
foreach (var infos in verificatInfos.Values)
|
||||
{
|
||||
foreach (var item in infos)
|
||||
{
|
||||
item.ClientIds.Clear();
|
||||
}
|
||||
}
|
||||
verificatInfoCacheService.HashSet(verificatInfos);//更新
|
||||
var verificatInfoService = app.ApplicationServices.GetService<IVerificatInfoService>();
|
||||
verificatInfoService.RemoveAllClientId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<PackageReference Include="UAParser" Version="3.1.47" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.158" />
|
||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="8.6.1" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="8.6.2" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ public class ClearTokenUtil
|
||||
{
|
||||
private static IRelationService RelationService;
|
||||
private static ISysUserService SysUserService;
|
||||
private static IVerificatInfoCacheService VerificatInfoCacheService;
|
||||
private static IVerificatInfoService VerificatInfoService;
|
||||
|
||||
/// <summary>
|
||||
/// 根据角色ID列表清除用户缓存
|
||||
@@ -59,15 +59,11 @@ public class ClearTokenUtil
|
||||
//从cache中删除用户信息
|
||||
SysUserService.DeleteUserFromCache(userIds);
|
||||
|
||||
VerificatInfoCacheService ??= App.RootServices!.GetRequiredService<IVerificatInfoCacheService>();//获取服务
|
||||
|
||||
var verificatInfos = userIds.Select(a => VerificatInfoCacheService.HashGetOne(a)).ToList();
|
||||
VerificatInfoService ??= App.RootServices!.GetRequiredService<IVerificatInfoService>();//获取服务
|
||||
|
||||
var verificatInfoIds = userIds.SelectMany(a => VerificatInfoService.GetListByUserId(a)).ToList();
|
||||
//从cache中删除用户token
|
||||
VerificatInfoCacheService.HashDel(userIds.ToArray());
|
||||
foreach (var item in verificatInfos)
|
||||
{
|
||||
await NoticeUtil.UserLoginOut(new UserLoginOutEvent() { VerificatInfos = item, Message = App.CreateLocalizerByType(typeof(SysUser))["ExitVerificat"] }).ConfigureAwait(false);
|
||||
}
|
||||
VerificatInfoService.Delete(verificatInfoIds.Select(a => a.Id).ToList());
|
||||
await NoticeUtil.UserLoginOut(new UserLoginOutEvent() { ClientIds = verificatInfoIds.SelectMany(a => a.ClientIds).ToList(), Message = App.CreateLocalizerByType(typeof(SysUser))["ExitVerificat"] }).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace ThingsGateway.Admin.Application;
|
||||
public class NoticeUtil
|
||||
{
|
||||
private static ISignalrNoticeService NoticeService;
|
||||
private static IVerificatInfoCacheService VerificatInfoCacheService;
|
||||
|
||||
/// <summary>
|
||||
/// 通知用户下线事件
|
||||
@@ -26,34 +25,7 @@ public class NoticeUtil
|
||||
{
|
||||
NoticeService ??= App.RootServices!.GetRequiredService<ISignalrNoticeService>();//获取服务
|
||||
//遍历verificat列表获取客户端ID列表
|
||||
await NoticeService.UserLoginOut(userLoginOutEvent?.VerificatInfos?.SelectMany(a => a.ClientIds), userLoginOutEvent!.Message).ConfigureAwait(false);//发送消息
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 有新的消息
|
||||
/// </summary>
|
||||
/// <param name="newMessageEvent"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task NewMessage(NewMessageEvent newMessageEvent)
|
||||
{
|
||||
VerificatInfoCacheService ??= App.RootServices!.GetRequiredService<IVerificatInfoCacheService>();//获取服务
|
||||
var clientIds = new List<long>();
|
||||
//获取用户verificat列表
|
||||
var verificatInfos = VerificatInfoCacheService.HashGet(newMessageEvent.UserIds.ToArray()).ToList();
|
||||
verificatInfos.ForEach(it =>
|
||||
{
|
||||
if (it != null)
|
||||
{
|
||||
it = it.Where(it => it.VerificatTimeout > DateTime.Now).ToList();//去掉登录超时的
|
||||
//遍历verificat
|
||||
it.ForEach(it =>
|
||||
{
|
||||
clientIds.AddRange(it.ClientIds);//添加到客户端ID列表
|
||||
});
|
||||
}
|
||||
});
|
||||
NoticeService ??= App.RootServices!.GetRequiredService<ISignalrNoticeService>();//获取服务
|
||||
await NoticeService.NewMesage(clientIds, newMessageEvent.Message).ConfigureAwait(false);//发送消息
|
||||
await NoticeService.UserLoginOut(userLoginOutEvent?.ClientIds, userLoginOutEvent!.Message).ConfigureAwait(false);//发送消息
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +38,7 @@ public class UserLoginOutEvent
|
||||
/// verificat信息
|
||||
/// </summary>
|
||||
|
||||
public IEnumerable<VerificatInfo>? VerificatInfos { get; set; }
|
||||
public List<long>? ClientIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 内容
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
ShowColumnList=ShowColumnList ExtendButtonColumnWidth="@ExtendButtonColumnWidth"
|
||||
CustomerSearchModel="CustomerSearchModel" SelectedRows="SelectedRows" ModelEqualityComparer="ModelEqualityComparer!"
|
||||
ShowExtendEditButtonCallback="ShowEditButtonCallback!" ShowExtendDeleteButtonCallback="ShowDeleteButtonCallback!"
|
||||
DisableExtendEditButton="DisableExtendEditButton!" DisableExtendDeleteButton="DisableExtendDeleteButton!"
|
||||
DisableExtendEditButtonCallback="DisableExtendEditButtonCallback!" DisableExtendDeleteButtonCallback="DisableExtendDeleteButtonCallback!"
|
||||
SetRowClassFormatter="SetRowClassFormatter!" OnAfterSaveAsync="OnAfterSaveAsync!" OnAfterDeleteAsync="OnAfterDeleteAsync!"
|
||||
OnAfterModifyAsync="OnAfterModifyAsync!" AutoGenerateColumns="AutoGenerateColumns"
|
||||
TableToolbarTemplate="TableToolbarTemplate" TableToolbarBeforeTemplate="TableToolbarBeforeTemplate!" TableColumns="TableColumns" EditTemplate="EditTemplate!"
|
||||
CustomerSearchTemplate="CustomerSearchTemplate!" RowButtonTemplate="RowButtonTemplate!" BeforeRowButtonTemplate="BeforeRowButtonTemplate!">
|
||||
|
||||
@@ -206,6 +206,26 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
||||
[Parameter]
|
||||
public bool? ShowExtendEditButton { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.ShowExtendEditButtonCallback"/>
|
||||
[Parameter]
|
||||
public Func<TItem, bool>? ShowExtendEditButtonCallback { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.DisableExtendEditButton"/>
|
||||
[Parameter]
|
||||
public bool DisableExtendEditButton { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.DisableExtendEditButtonCallback"/>
|
||||
[Parameter]
|
||||
public Func<TItem, bool>? DisableExtendEditButtonCallback { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.DisableExtendDeleteButton"/>
|
||||
[Parameter]
|
||||
public bool DisableExtendDeleteButton { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.DisableExtendDeleteButtonCallback"/>
|
||||
[Parameter]
|
||||
public Func<TItem, bool>? DisableExtendDeleteButtonCallback { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.ShowExportButton"/>
|
||||
[Parameter]
|
||||
public bool ShowExportButton { get; set; } = false;
|
||||
@@ -234,6 +254,10 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
||||
[Parameter]
|
||||
public bool? ShowExtendDeleteButton { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.ShowExtendDeleteButtonCallback"/>
|
||||
[Parameter]
|
||||
public Func<TItem, bool>? ShowExtendDeleteButtonCallback { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.ShowExtendButtons"/>
|
||||
[Parameter]
|
||||
public bool ShowExtendButtons { get; set; } = false;
|
||||
@@ -282,6 +306,18 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
||||
[Parameter]
|
||||
public Func<TItem, bool>? ShowDeleteButtonCallback { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SetRowClassFormatter"/>
|
||||
[Parameter]
|
||||
public Func<TItem, string?>? SetRowClassFormatter { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.OnAfterSaveAsync"/>
|
||||
[Parameter]
|
||||
public Func<TItem, Task>? OnAfterSaveAsync { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.OnAfterDeleteAsync"/>
|
||||
[Parameter]
|
||||
public Func<List<TItem>, Task>? OnAfterDeleteAsync { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.OnAfterModifyAsync"/>
|
||||
[Parameter]
|
||||
public Func<Task>? OnAfterModifyAsync { get; set; }
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
@page "/Account/AccessDenied"
|
||||
@layout BaseLayout
|
||||
@namespace ThingsGateway.Admin.Razor
|
||||
@using Microsoft.AspNetCore.Authentication.Cookies
|
||||
@using Microsoft.AspNetCore.WebUtilities
|
||||
@using ThingsGateway.Admin.Application;
|
||||
@using ThingsGateway.Core.Extension;
|
||||
@using ThingsGateway.Razor
|
||||
@@ -9,6 +11,20 @@
|
||||
<img alt="401" src="@($"{WebsiteConst.DefaultResourceUrl}images/401.png")" style="max-width: 100%;" />
|
||||
</div>
|
||||
<h5>@Localizer["401"]</h5>
|
||||
<Button class="mt-2" Color="Color.Primary" OnClick=@(()=>NavigationManager.NavigateTo("/"))>@Localizer["Home"]</Button>
|
||||
<div>
|
||||
|
||||
<Button class="mx-2" Color="Color.Primary" OnClick=@(()=>
|
||||
{
|
||||
NavigationManager.NavigateTo(QueryHelpers.AddQueryString(CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
|
||||
{
|
||||
["ReturnUrl"] = ReturnUrl
|
||||
})
|
||||
);
|
||||
}
|
||||
)>
|
||||
@Localizer["Login"]
|
||||
</Button>
|
||||
<Button class="mx-2" Color="Color.Primary" OnClick=@(()=>NavigationManager.NavigateTo("/"))>@Localizer["Home"]</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@ public partial class AccessDenied
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IStringLocalizer<AccessDenied>? Localizer { get; set; }
|
||||
|
||||
[SupplyParameterFromQuery]
|
||||
[Parameter]
|
||||
public string? ReturnUrl { get; set; }
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private NavigationManager? NavigationManager { get; set; }
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<span class="avatar">
|
||||
@WebsiteOption.Value.Title?.GetNameLen2()
|
||||
</span>
|
||||
<h4>@WebsiteOption.Value.Title</h4>
|
||||
<h4>@($"{WebsiteOption.Value.Title} {_versionString}")</h4>
|
||||
</div>
|
||||
<div class="d-flex flex-column align-items-start ms-5 mt-5 mb-auto">
|
||||
<div class="mb-3">
|
||||
|
||||
@@ -41,12 +41,21 @@ public partial class Login
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private ToastService? ToastService { get; set; }
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IAppVersionService? VersionService { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IOptions<WebsiteOptions>? WebsiteOption { get; set; }
|
||||
private string _versionString = string.Empty;
|
||||
|
||||
private Task OnChanged(BreakPoint breakPoint)
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
_versionString = $"v{VersionService.Version}";
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
private Task OnChanged(BreakPoint breakPoint)
|
||||
{
|
||||
authDeviceTypeEnum = breakPoint > BreakPoint.Medium ? AuthDeviceTypeEnum.PC : AuthDeviceTypeEnum.APP;
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
@* 切换模块 *@
|
||||
<a @onclick="ChoiceModule"><i class="fas fa-arrow-right-arrow-left me-2" />@Localizer["ChoiceModule"]</a>
|
||||
<a @onclick="@OnUserInfoDialog"><i class="fa-solid fa-suitcase me-2"></i>@Localizer["UserCenter"]</a>
|
||||
<LogoutLink Url=@("api/auth/logout?returnUrl="+NavigationManager.ToBaseRelativePath(NavigationManager.Uri)) />
|
||||
<a @onclick="@LogoutAsync"><i class="fa-solid fa-key me-2"></i>@Localizer["Logout"]</a>
|
||||
|
||||
</LinkTemplate>
|
||||
</Logout>
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Core;
|
||||
using ThingsGateway.Core.Json.Extension;
|
||||
|
||||
namespace ThingsGateway.Admin.Razor;
|
||||
|
||||
@@ -119,6 +123,44 @@ public partial class MainLayout : IDisposable
|
||||
|
||||
#endregion 个人信息修改
|
||||
|
||||
#region 注销
|
||||
[Inject]
|
||||
AjaxService AjaxService { get; set; }
|
||||
private async Task LogoutAsync()
|
||||
{
|
||||
var ajaxOption = new AjaxOption
|
||||
{
|
||||
Url = "/api/auth/logout",
|
||||
Method = "POST",
|
||||
};
|
||||
using var str = await AjaxService.InvokeAsync(ajaxOption);
|
||||
if (str != null)
|
||||
{
|
||||
var ret = str.RootElement.GetRawText().FromSystemTextJsonString<UnifyResult<object>>();
|
||||
if (ret.Code != 200)
|
||||
{
|
||||
await ToastService.Error(Localizer["LoginErrorh1"], $"{ret.Msg}");
|
||||
}
|
||||
else
|
||||
{
|
||||
await ToastService.Information(Localizer["LoginSuccessh1"], Localizer["LoginSuccessc1"]);
|
||||
await Task.Delay(1000);
|
||||
var url = QueryHelpers.AddQueryString(CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
|
||||
{
|
||||
["ReturnUrl"] = NavigationManager.ToBaseRelativePath(NavigationManager.Uri)
|
||||
});
|
||||
await AjaxService.Goto(url);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ToastService.Error(Localizer["LoginErrorh2"], Localizer["LoginErrorc2"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task ShowAbout()
|
||||
{
|
||||
DialogOption? op = null;
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
"ThingsGateway.Admin.Razor.AccessDenied": {
|
||||
"404": "Sorry, the page you are looking for does not exist.",
|
||||
"401": "Sorry, you do not have permission to access this page.",
|
||||
"Home": "Back to Home"
|
||||
"Home": "Back to Home",
|
||||
"Login": "Login"
|
||||
},
|
||||
"ThingsGateway.Admin.Razor.Login": {
|
||||
"LoginErrorh1": "Login Error",
|
||||
@@ -47,6 +48,13 @@
|
||||
"ChoiceModule": "Switch Module",
|
||||
"GiteePostTitleText": "Code Commit Push Notification",
|
||||
|
||||
"LoginErrorh1": "Login Error",
|
||||
"LoginSuccessh1": "Login Success",
|
||||
"LoginSuccessc1": "Redirecting to the page",
|
||||
"LoginErrorh2": "Login Failed",
|
||||
"LoginErrorc2": "Please contact the administrator!",
|
||||
"Logout": "Logout",
|
||||
|
||||
"系统首页": "Home",
|
||||
"权限管理": "Permission",
|
||||
"用户管理": "User",
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
"ThingsGateway.Admin.Razor.AccessDenied": {
|
||||
"404": "抱歉,您访问的页面不存在。",
|
||||
"401": "抱歉,您无权限访问该页面。",
|
||||
"Home": "回到首页"
|
||||
"Home": "回到首页",
|
||||
"Login": "重新登录"
|
||||
},
|
||||
|
||||
"ThingsGateway.Admin.Razor.Login": {
|
||||
@@ -48,6 +49,14 @@
|
||||
"ChoiceModule": "切换模块",
|
||||
"GiteePostTitleText": "代码提交推送通知",
|
||||
|
||||
|
||||
"LoginErrorh1": "登录异常",
|
||||
"LoginSuccessh1": "登录成功",
|
||||
"LoginSuccessc1": "即将跳转页面",
|
||||
"LoginErrorh2": "登录失败",
|
||||
"LoginErrorc2": "请联系管理员!",
|
||||
"Logout": "注销",
|
||||
|
||||
"系统首页": "系统首页",
|
||||
"权限管理": "权限管理",
|
||||
"用户管理": "用户管理",
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mx-1 form-inline">
|
||||
<div class="col-12 col-md-12 sh">
|
||||
<Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
|
||||
<HeaderTemplate>
|
||||
@Localizer["HardwareInfoChart"]
|
||||
@@ -55,5 +57,7 @@
|
||||
<Chart @ref=CPULineChart OnInitAsync="OnCPUInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="()=>{chartInit=true;return Task.CompletedTask;}" />
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace ThingsGateway.Core;
|
||||
/// </summary>
|
||||
public partial interface ICacheService
|
||||
{
|
||||
|
||||
#region 基础操作
|
||||
|
||||
T? GetOrCreate<T>(string key, Func<string, T> callback, int expire = -1);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace ThingsGateway.Core;
|
||||
/// <inheritdoc cref="ICacheService"/>
|
||||
/// 内存缓存
|
||||
/// </summary>
|
||||
public partial class MemoryCacheService : ICacheService
|
||||
public partial class MemoryCacheService : ICacheService,IDisposable
|
||||
{
|
||||
private readonly MemoryCache _memoryCache;
|
||||
|
||||
@@ -27,6 +27,15 @@ public partial class MemoryCacheService : ICacheService
|
||||
{
|
||||
_memoryCache = new MemoryCache();
|
||||
}
|
||||
~ MemoryCacheService()
|
||||
{
|
||||
this.Dispose();
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_memoryCache.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#region 普通操作
|
||||
|
||||
@@ -229,5 +238,7 @@ public partial class MemoryCacheService : ICacheService
|
||||
return _memoryCache.AcquireLock(key, msTimeout, msExpire, throwOnFailure);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion 事务
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
@@ -60,7 +63,7 @@ public static class ChannelConfigExtensions
|
||||
/// 获取一个新的Tcp客户端通道。传入远程服务端地址
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static TcpClientChannel GetTcpClientWithIPHost(this TouchSocketConfig config, IPHost remoteUrl, string? bindUrl = default)
|
||||
public static TcpClientChannel GetTcpClientWithIPHost(this TouchSocketConfig config, string remoteUrl, string? bindUrl = default)
|
||||
{
|
||||
if (remoteUrl == null)
|
||||
throw new ArgumentNullException(nameof(IPHost));
|
||||
@@ -77,10 +80,11 @@ public static class ChannelConfigExtensions
|
||||
/// 获取一个新的Tcp服务端通道。传入绑定地址
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static TcpServiceChannel GetTcpServiceWithBindIPHost(this TouchSocketConfig config, IPHost bindUrl)
|
||||
public static TcpServiceChannel GetTcpServiceWithBindIPHost(this TouchSocketConfig config, string bindUrl)
|
||||
{
|
||||
if (bindUrl == null) throw new ArgumentNullException(nameof(IPHost));
|
||||
config.SetListenIPHosts(new IPHost[] { bindUrl });
|
||||
var urls= bindUrl.SplitStringBySemicolon();
|
||||
config.SetListenIPHosts(IPHost.ParseIPHosts(urls));
|
||||
//载入配置
|
||||
TcpServiceChannel tcpServiceChannel = new TcpServiceChannel();
|
||||
tcpServiceChannel.Setup(config);
|
||||
|
||||
@@ -79,7 +79,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
{
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
if (Monitor == null)
|
||||
Logger.Info($"{Monitor.IPHost}{DefaultResource.Localizer["ServiceStoped"]}");
|
||||
Logger.Info($"{DefaultResource.Localizer["ServiceStoped"]}");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 通道配置保存
|
||||
/// </summary>
|
||||
public class ChannelConfig : AppConfigBase
|
||||
{
|
||||
public static ChannelConfig Default;
|
||||
|
||||
static ChannelConfig()
|
||||
{
|
||||
Default = AppConfigBase.GetNewDefault<ChannelConfig>();
|
||||
foreach (var item in Default.ChannelDatas)
|
||||
{
|
||||
ChannelData.CreateChannel(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通道列表
|
||||
/// </summary>
|
||||
public ConcurrentList<ChannelData> ChannelDatas { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public ChannelConfig() : base(($"channel.config.json"))
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,7 @@ public class ChannelData
|
||||
{
|
||||
if (channelData.Channel != null)
|
||||
{
|
||||
channelData.Channel.Close();
|
||||
channelData.Channel.SafeDispose();
|
||||
}
|
||||
channelData.TouchSocketConfig?.Dispose();
|
||||
|
||||
@@ -11,13 +11,33 @@
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc cref="IResultMessage"/>
|
||||
public abstract class MessageBase : OperResultClass<byte[]>, IResultMessage, IWaitHandle
|
||||
public class MessageBase : OperResultClass<byte[]>, IResultMessage, IWaitHandle
|
||||
{
|
||||
public MessageBase()
|
||||
{
|
||||
}
|
||||
|
||||
public MessageBase(IOperResult operResult) : base(operResult)
|
||||
{
|
||||
}
|
||||
|
||||
public MessageBase(string msg) : base(msg)
|
||||
{
|
||||
}
|
||||
|
||||
public MessageBase(Exception ex) : base(ex)
|
||||
{
|
||||
}
|
||||
|
||||
public MessageBase(string msg, Exception ex) : base(msg, ex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int BodyLength { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract int HeadBytesLength { get; }
|
||||
public virtual int HeadBytesLength { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int Sign { get; set; }
|
||||
@@ -26,7 +46,10 @@ public abstract class MessageBase : OperResultClass<byte[]>, IResultMessage, IWa
|
||||
public IByteBlock ReceivedBytes { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract bool CheckHeadBytes(byte[]? headBytes);
|
||||
public virtual bool CheckHeadBytes(byte[]? headBytes)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void SendInfo(byte[]? sendBytes)
|
||||
|
||||
@@ -23,6 +23,25 @@ public abstract class ReadWriteDevicesSingleStreamDataHandleAdapter<TRequest> :
|
||||
CacheTimeoutEnable = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在解析时发生错误。
|
||||
/// </summary>
|
||||
/// <param name="ex">异常</param>
|
||||
/// <param name="error">错误异常</param>
|
||||
/// <param name="reset">是否调用Reset/></param>
|
||||
/// <param name="log">是否记录日志</param>
|
||||
protected override void OnError(Exception ex, string error, bool reset, bool log)
|
||||
{
|
||||
if (reset)
|
||||
{
|
||||
this.Reset();
|
||||
}
|
||||
if (log)
|
||||
{
|
||||
this.Logger?.LogError(ex, error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSendRequestInfo => true;
|
||||
|
||||
@@ -63,8 +82,6 @@ public abstract class ReadWriteDevicesSingleStreamDataHandleAdapter<TRequest> :
|
||||
|
||||
protected override FilterResult Filter<TByteBlock>(ref TByteBlock byteBlock, bool beCached, ref TRequest request, ref int tempCapacity)
|
||||
{
|
||||
//整个流程都不会改变流的游标位置,所以对于是否缓存的情况都是一样的处理
|
||||
|
||||
if (Logger.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Receive:{(IsHexData ? byteBlock.AsSegment().ToHexString() : byteBlock.ToString())}");
|
||||
{
|
||||
@@ -77,6 +94,8 @@ public abstract class ReadWriteDevicesSingleStreamDataHandleAdapter<TRequest> :
|
||||
request = GetInstance();
|
||||
}
|
||||
|
||||
var pos = byteBlock.Position;
|
||||
|
||||
if (request.HeadBytesLength > byteBlock.CanReadLength)
|
||||
{
|
||||
return FilterResult.Cache;//当头部都无法解析时,直接缓存
|
||||
@@ -86,7 +105,7 @@ public abstract class ReadWriteDevicesSingleStreamDataHandleAdapter<TRequest> :
|
||||
//当解析消息设定固定头长度大于0时,获取头部字节
|
||||
if (request.HeadBytesLength > 0)
|
||||
{
|
||||
header = byteBlock.AsSegment(0, request.HeadBytesLength);
|
||||
header = byteBlock.AsSegment(byteBlock.Position, request.HeadBytesLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -125,7 +144,7 @@ public abstract class ReadWriteDevicesSingleStreamDataHandleAdapter<TRequest> :
|
||||
}
|
||||
else if (result.FilterResult == FilterResult.Success)
|
||||
{
|
||||
byteBlock.Position = byteBlock.Length;
|
||||
byteBlock.Position = request.HeadBytesLength + request.BodyLength + pos;
|
||||
if (request.IsSuccess)
|
||||
{
|
||||
request.Content = result.Content;
|
||||
|
||||
@@ -37,6 +37,25 @@ public abstract class ReadWriteDevicesUdpDataHandleAdapter<TRequest> : UdpDataHa
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsSendPackCommand { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 在解析时发生错误。
|
||||
/// </summary>
|
||||
/// <param name="ex">异常</param>
|
||||
/// <param name="error">错误异常</param>
|
||||
/// <param name="reset">是否调用Reset/></param>
|
||||
/// <param name="log">是否记录日志</param>
|
||||
protected override void OnError(Exception ex, string error, bool reset, bool log)
|
||||
{
|
||||
if (reset)
|
||||
{
|
||||
this.Reset();
|
||||
}
|
||||
if (log)
|
||||
{
|
||||
this.Logger?.LogError(ex, error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送前,对当前的命令进行打包处理
|
||||
/// </summary>
|
||||
@@ -82,12 +101,10 @@ public abstract class ReadWriteDevicesUdpDataHandleAdapter<TRequest> : UdpDataHa
|
||||
Logger?.Trace($"{ToString()}- Receive:{(IsHexData ? byteBlock.AsSegment().ToHexString() : byteBlock.ToString())}");
|
||||
|
||||
TRequest request = null;
|
||||
//非并发协议,复用对象
|
||||
if (IsSingleThread)
|
||||
request = request == null ? GetInstance() : request;
|
||||
request = Request == null ? GetInstance() : Request;
|
||||
else
|
||||
{
|
||||
//并发协议非缓存模式下,重新获取对象
|
||||
request = GetInstance();
|
||||
}
|
||||
ArraySegment<byte>? header = null;
|
||||
|
||||
@@ -466,4 +466,18 @@ public interface IProtocol : IDisposable
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <returns></returns>
|
||||
bool BitReverse(string address);
|
||||
|
||||
/// <summary>
|
||||
/// 断开连接
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <returns></returns>
|
||||
Task Close(string msg = null);
|
||||
|
||||
/// <summary>
|
||||
/// 连接
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task ConnectAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using NewLife.Extension;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
@@ -26,6 +30,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
|
||||
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
|
||||
{
|
||||
var len = DtuService.HeartbeatHexString.HexStringToBytes().Length;
|
||||
if (client is TcpSessionClientChannel socket)
|
||||
{
|
||||
if (!socket.Id.StartsWith("ID="))
|
||||
@@ -35,13 +40,16 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
client.Logger.Info(DefaultResource.Localizer["DtuConnected", id]);
|
||||
e.Handled = true;
|
||||
}
|
||||
if (DtuService.HeartbeatHexString == e.ByteBlock.AsSegment().ToHexString(default))
|
||||
if (len > 0)
|
||||
{
|
||||
//回应心跳包
|
||||
socket.Send(e.ByteBlock.AsSegment());
|
||||
e.Handled = true;
|
||||
if (socket.Logger.LogLevel <= LogLevel.Trace)
|
||||
socket.Logger?.Trace($"{socket}- Heartbeat");
|
||||
if (DtuService.HeartbeatHexString == e.ByteBlock.AsSegment(0, len).ToHexString(default))
|
||||
{
|
||||
//回应心跳包
|
||||
socket.Send(e.ByteBlock.AsSegment());
|
||||
e.Handled = true;
|
||||
if (socket.Logger.LogLevel <= LogLevel.Trace)
|
||||
socket.Logger?.Trace($"{socket}- Heartbeat");
|
||||
}
|
||||
}
|
||||
}
|
||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Drawing;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
@@ -78,11 +79,15 @@ internal class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugin, ITcp
|
||||
|
||||
if (client is ITcpClient tcpClient)
|
||||
{
|
||||
if (DtuClient.HeartbeatHexString == e.ByteBlock.AsSegment().ToHexString(default))
|
||||
var len = DtuClient.HeartbeatHexString.HexStringToBytes().Length;
|
||||
if (len > 0)
|
||||
{
|
||||
e.Handled = true;
|
||||
if (DtuClient.HeartbeatHexString == e.ByteBlock.AsSegment(0, len).ToHexString(default))
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||
}
|
||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +204,18 @@ public abstract class ProtocolBase : DisposableObject, IProtocol
|
||||
|
||||
#region 设备异步返回
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task ConnectAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Channel.ConnectAsync(ConnectTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task Close(string msg = default)
|
||||
{
|
||||
return Channel.CloseAsync(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 接收,非主动发送的情况,重写实现非主从并发通讯协议
|
||||
/// </summary>
|
||||
@@ -335,9 +347,16 @@ public abstract class ProtocolBase : DisposableObject, IProtocol
|
||||
waitData.SetCancellationToken(cancellationToken);
|
||||
await clientChannel.SendAsync(item).ConfigureAwait(false);
|
||||
var waitDataStatus = await waitData.WaitAsync(timeout).ConfigureAwait(false);
|
||||
waitDataStatus.ThrowIfNotRunning();
|
||||
var response = waitData.WaitResult;
|
||||
return response;
|
||||
var result = waitDataStatus.Check();
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
var response = waitData.WaitResult;
|
||||
return response;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new MessageBase(result);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.Resources;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 协议基类
|
||||
/// </summary>
|
||||
public static class ProtocolBaseExtension
|
||||
public static partial class ProtocolBaseExtension
|
||||
{
|
||||
#region 读取
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.Resources;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
public static partial class ProtocolWaitDataStatusExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 当状态不是<see cref="WaitDataStatus.SetRunning"/>时返回异常。
|
||||
/// </summary>
|
||||
/// <param name="status"></param>
|
||||
public static OperResult Check(this WaitDataStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case WaitDataStatus.SetRunning:
|
||||
return new();
|
||||
|
||||
case WaitDataStatus.Canceled: return new(new OperationCanceledException());
|
||||
case WaitDataStatus.Overtime: return new(new TimeoutException());
|
||||
case WaitDataStatus.Disposed:
|
||||
case WaitDataStatus.Default:
|
||||
default:
|
||||
{
|
||||
return new(new Exception(TouchSocketCoreResource.UnknownError));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,22 @@ public partial class ThingsGatewayBitConverter : IThingsGatewayBitConverter
|
||||
|
||||
internal TouchSocketBitConverter TouchSocketBitConverter => TouchSocketBitConverter.GetBitConverter(EndianType);
|
||||
|
||||
static ThingsGatewayBitConverter()
|
||||
{
|
||||
BigEndian = new ThingsGatewayBitConverter(EndianType.Big);
|
||||
LittleEndian = new ThingsGatewayBitConverter(EndianType.Little);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以大端
|
||||
/// </summary>
|
||||
public static readonly ThingsGatewayBitConverter BigEndian;
|
||||
|
||||
/// <summary>
|
||||
/// 以小端
|
||||
/// </summary>
|
||||
public static readonly ThingsGatewayBitConverter LittleEndian;
|
||||
|
||||
#region GetBytes
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -15,6 +15,7 @@ using Newtonsoft.Json.Linq;
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
using ThingsGateway.Foundation.Json.Extension;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
@@ -37,7 +38,7 @@ public static class ThingsGatewayBitConverterExtension
|
||||
|
||||
var type = defaultBitConverter.GetType();
|
||||
// 尝试从缓存中获取解析结果
|
||||
var cacheKey = $"{nameof(ThingsGatewayBitConverterExtension)}_{nameof(GetTransByAddress)}_{type.FullName}_{defaultBitConverter.ToJsonString()}_{registerAddress}";
|
||||
var cacheKey = $"{nameof(ThingsGatewayBitConverterExtension)}_{nameof(GetTransByAddress)}_{type.FullName}_{defaultBitConverter.ToJsonNetString()}_{registerAddress}";
|
||||
if (MemoryCache.Instance.TryGetValue(cacheKey, out IThingsGatewayBitConverter cachedConverter))
|
||||
{
|
||||
return (IThingsGatewayBitConverter)cachedConverter!.Map(type);
|
||||
|
||||
@@ -25,21 +25,30 @@ public static class VariableValuePraseExtensions
|
||||
/// <param name="buffer">返回的字节数组</param>
|
||||
/// <param name="exWhenAny">任意一个失败时抛出异常</param>
|
||||
/// <returns>解析结果</returns>
|
||||
public static void PraseStructContent<T>(this IEnumerable<T> variables, IProtocol protocol, byte[] buffer, bool exWhenAny = false) where T : IVariable
|
||||
public static OperResult PraseStructContent<T>(this IEnumerable<T> variables, IProtocol protocol, byte[] buffer, bool exWhenAny) where T : IVariable
|
||||
{
|
||||
foreach (var variable in variables)
|
||||
{
|
||||
IThingsGatewayBitConverter byteConverter = variable.ThingsGatewayBitConverter;
|
||||
var dataType = variable.DataType;
|
||||
int index = variable.Index;
|
||||
var data = byteConverter.GetDataFormBytes(protocol, variable.RegisterAddress, buffer, index, dataType);
|
||||
Set(variable, data);
|
||||
try
|
||||
{
|
||||
var data = byteConverter.GetDataFormBytes(protocol, variable.RegisterAddress, buffer, index, dataType);
|
||||
var result = Set(variable, data);
|
||||
if (exWhenAny)
|
||||
if (!result.IsSuccess)
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult($"Error parsing byte array, address: {variable.RegisterAddress}, array length: {buffer.Length}, index: {index}, type: {dataType}", ex);
|
||||
}
|
||||
}
|
||||
void Set(IVariable organizedVariable, object num)
|
||||
return new();
|
||||
OperResult Set(IVariable organizedVariable, object num)
|
||||
{
|
||||
var result = organizedVariable.SetValue(num);
|
||||
if (!result.IsSuccess && exWhenAny)
|
||||
throw result.Exception ?? new Exception(result.ErrorMessage);
|
||||
return organizedVariable.SetValue(num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,28 +22,27 @@ public class CRC16Utils
|
||||
/// <returns>返回校验成功与否</returns>
|
||||
public static bool CheckCRC16(byte[] value)
|
||||
{
|
||||
return CheckCRC16(value, 160, 1);
|
||||
return CheckCRC16(value, 0, value.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定多项式码来校验对应的接收数据的CRC校验码
|
||||
/// </summary>
|
||||
/// <param name="value">需要校验的数据,带CRC校验码</param>
|
||||
/// <param name="CH">多项式码高位</param>
|
||||
/// <param name="CL">多项式码低位</param>
|
||||
/// <param name="index">计算的起始字节索引</param>
|
||||
/// <param name="length">计算的字节长度</param>
|
||||
/// <returns>返回校验成功与否</returns>
|
||||
public static bool CheckCRC16(byte[] value, byte CH, byte CL)
|
||||
public static bool CheckCRC16(byte[] value, int index, int length)
|
||||
{
|
||||
if (value == null || value.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int length = value.Length;
|
||||
byte[] destinationArray = new byte[length - 2];
|
||||
Array.Copy(value, 0, destinationArray, 0, destinationArray.Length);
|
||||
byte[] numArray = CRC16(destinationArray, CH, CL);
|
||||
return numArray[length - 2] == value[length - 2] && numArray[length - 1] == value[length - 1];
|
||||
Array.Copy(value, index, destinationArray, 0, destinationArray.Length);
|
||||
byte[] numArray = Crc16Only(destinationArray);
|
||||
return numArray[0] == value[length - 2] && numArray[1] == value[length - 1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -51,49 +50,23 @@ public class CRC16Utils
|
||||
/// </summary>
|
||||
/// <param name="value">需要校验的数据,不包含CRC字节</param>
|
||||
/// <returns>返回带CRC校验码的字节数组,可用于串口发送</returns>
|
||||
public static byte[] CRC16(byte[] value)
|
||||
public static byte[] Crc16(byte[] value)
|
||||
{
|
||||
return CRC16(value, 160, 1);
|
||||
return Crc16(value, 0, value.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过指定多项式码来获取对应的数据的CRC校验码
|
||||
/// </summary>
|
||||
/// <param name="value">需要校验的数据,不包含CRC字节</param>
|
||||
/// <param name="CL">多项式码地位</param>
|
||||
/// <param name="CH">多项式码高位</param>
|
||||
/// <param name="preH">预置的高位值</param>
|
||||
/// <param name="preL">预置的低位值</param>
|
||||
/// <returns>返回带CRC校验码的字节数组,可用于串口发送</returns>
|
||||
public static byte[] CRC16(byte[] value, byte CH, byte CL, byte preH = 255, byte preL = 255)
|
||||
/// <param name="index">计算的起始字节索引</param>
|
||||
/// <param name="length">计算的字节长度</param>
|
||||
public static byte[] Crc16(byte[] value, int index, int length)
|
||||
{
|
||||
byte[] numArray = new byte[value.Length + 2];
|
||||
value.CopyTo(numArray, 0);
|
||||
byte num1 = preL;
|
||||
byte num2 = preH;
|
||||
foreach (byte num3 in value)
|
||||
{
|
||||
num1 ^= num3;
|
||||
for (int index = 0; index <= 7; ++index)
|
||||
{
|
||||
byte num4 = num2;
|
||||
byte num5 = num1;
|
||||
num2 >>= 1;
|
||||
num1 >>= 1;
|
||||
if ((num4 & 1) == 1)
|
||||
{
|
||||
num1 |= 128;
|
||||
}
|
||||
|
||||
if ((num5 & 1) == 1)
|
||||
{
|
||||
num2 ^= CH;
|
||||
num1 ^= CL;
|
||||
}
|
||||
}
|
||||
}
|
||||
numArray[numArray.Length - 2] = num1;
|
||||
numArray[numArray.Length - 1] = num2;
|
||||
Array.Copy(value, index, numArray, 0, length);
|
||||
var crc = Crc16Only(value, index, length);
|
||||
Array.Copy(crc, 0, numArray, numArray.Length - 2, 2);
|
||||
return numArray;
|
||||
}
|
||||
|
||||
@@ -101,14 +74,10 @@ public class CRC16Utils
|
||||
/// 通过指定多项式码来获取对应的数据的CRC校验码
|
||||
/// </summary>
|
||||
/// <param name="value">需要校验的数据,不包含CRC字节</param>
|
||||
/// <param name="index">计算的起始字节索引</param>
|
||||
/// <param name="length">计算的字节长度</param>
|
||||
/// <returns>返回带CRC校验码的字节数组,可用于串口发送</returns>
|
||||
public static byte[] CRC16Only(byte[] value,
|
||||
int index,
|
||||
int length)
|
||||
public static byte[] Crc16Only(byte[] value)
|
||||
{
|
||||
return CRC16Only(value, index, length, 160, 1);
|
||||
return Crc16Only(value, 0, value.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -117,40 +86,48 @@ public class CRC16Utils
|
||||
/// <param name="value">需要校验的数据,不包含CRC字节</param>
|
||||
/// <param name="index">计算的起始字节索引</param>
|
||||
/// <param name="length">计算的字节长度</param>
|
||||
/// <param name="CL">多项式码地位</param>
|
||||
/// <param name="CH">多项式码高位</param>
|
||||
/// <param name="preH">预置的高位值</param>
|
||||
/// <param name="preL">预置的低位值</param>
|
||||
/// <returns>返回带CRC校验码的字节数组,可用于串口发送</returns>
|
||||
public static byte[] CRC16Only(
|
||||
byte[] value,
|
||||
public static byte[] Crc16Only(byte[] value,
|
||||
int index,
|
||||
int length,
|
||||
byte CH,
|
||||
byte CL,
|
||||
byte preH = 255,
|
||||
byte preL = 255)
|
||||
int length)
|
||||
{
|
||||
byte num1 = preL;
|
||||
byte num2 = preH;
|
||||
for (int index1 = index; index1 < index + length; ++index1)
|
||||
return Crc16Only(value, index, length, 0xA001);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过指定多项式码来获取对应的数据的CRC校验码
|
||||
/// </summary>
|
||||
/// <param name="data">需要校验的数据,不包含CRC字节</param>
|
||||
/// <param name="index">计算的起始字节索引</param>
|
||||
/// <param name="length">计算的字节长度</param>
|
||||
/// <param name="xdapoly">多项式</param>
|
||||
/// <param name="crc16">crc16</param>
|
||||
/// <returns>返回带CRC校验码的字节数组,可用于串口发送</returns>
|
||||
public static byte[] Crc16Only(byte[] data, int index, int length, int xdapoly, bool crc16 = true)
|
||||
{
|
||||
int num = 65535;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
num1 ^= value[index1];
|
||||
for (int index2 = 0; index2 <= 7; ++index2)
|
||||
num = (num >> ((!crc16) ? 8 : 0)) ^ data[i];
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
byte num3 = num2;
|
||||
byte num4 = num1;
|
||||
num2 >>= 1;
|
||||
num1 >>= 1;
|
||||
if ((num3 & 1) == 1)
|
||||
num1 |= 128;
|
||||
if ((num4 & 1) == 1)
|
||||
int num2 = num & 1;
|
||||
num >>= 1;
|
||||
if (num2 == 1)
|
||||
{
|
||||
num2 ^= CH;
|
||||
num1 ^= CL;
|
||||
num ^= xdapoly;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new byte[2] { num1, num2 };
|
||||
|
||||
return (!crc16) ? new byte[2]
|
||||
{
|
||||
(byte)(num >> 8),
|
||||
(byte)((uint)num & 0xFFu)
|
||||
} : new byte[2]
|
||||
{
|
||||
(byte)((uint)num & 0xFFu),
|
||||
(byte)(num >> 8)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using CSScripting;
|
||||
|
||||
using CSScriptLib;
|
||||
|
||||
using NewLife.Caching;
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway.Core.Json.Extension;
|
||||
@@ -76,12 +80,12 @@ public class DemoData
|
||||
|
||||
public interface IDynamicModel
|
||||
{
|
||||
IEnumerable<dynamic> GetList(IEnumerable<dynamic> datas);
|
||||
IEnumerable<dynamic> GetList(IEnumerable<object> datas);
|
||||
}
|
||||
|
||||
public interface IDynamicModelData
|
||||
{
|
||||
dynamic GeData(dynamic datas);
|
||||
dynamic GeData(object datas);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -89,29 +93,97 @@ public interface IDynamicModelData
|
||||
/// </summary>
|
||||
public static class CSharpScriptEngineExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行脚本获取返回值,通常用于上传实体返回脚本,参数为input
|
||||
/// </summary>
|
||||
public static T Do<T>(string _source) where T : class
|
||||
|
||||
static CSharpScriptEngineExtension()
|
||||
{
|
||||
var cacheKey = $"{nameof(CSharpScriptEngineExtension)}-{Do<T>}-{_source}";
|
||||
var runscript = App.CacheService.GetOrCreate(cacheKey, c =>
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
var eva = CSScript.Evaluator
|
||||
.LoadCode<T>(
|
||||
@$"
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using ThingsGateway.Core.Json.Extension;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
{_source}
|
||||
");
|
||||
return eva;
|
||||
}, 3600);
|
||||
return runscript;
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(30000);
|
||||
//检测缓存
|
||||
try
|
||||
{
|
||||
var data = Instance.GetAll();
|
||||
m_waiterLock.Wait();
|
||||
|
||||
foreach (var item in data)
|
||||
{
|
||||
if (item.Value.ExpiredTime < item.Value.VisitTime + 1800_000)
|
||||
{
|
||||
Instance.Remove(item.Key);
|
||||
item.Value?.Value?.GetType().Assembly.Unload();
|
||||
GC.Collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_waiterLock.Release();
|
||||
}
|
||||
|
||||
await Task.Delay(30000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
static MemoryCache Instance { get; set; } = new MemoryCache();
|
||||
static EasyLock m_waiterLock = new EasyLock();
|
||||
static string CacheKey = $"{nameof(CSharpScriptEngineExtension)}-{nameof(Do)}";
|
||||
/// <summary>
|
||||
/// 执行脚本获取返回值ReadWriteExpressions
|
||||
/// </summary>
|
||||
public static T Do<T>(string source) where T : class
|
||||
{
|
||||
var field = $"{CacheKey}-{source}";
|
||||
var runScript = Instance.Get<T>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_waiterLock.Wait();
|
||||
runScript = Instance.Get<T>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
if (!source.Contains("return"))
|
||||
{
|
||||
source = $"return {source}";//只判断简单脚本中可省略return字符串
|
||||
}
|
||||
|
||||
// 动态加载并执行代码
|
||||
runScript = CSScript.Evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<T>(
|
||||
$@"
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using ThingsGateway.Core.Json.Extension;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
{source};
|
||||
");
|
||||
GC.Collect();
|
||||
Instance.Set(field, runScript);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_waiterLock.Release();
|
||||
}
|
||||
}
|
||||
Instance.SetExpire(field, TimeSpan.FromHours(1));
|
||||
|
||||
return runScript;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// GetDynamicModel
|
||||
/// </summary>
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using CSScripting;
|
||||
|
||||
using CSScriptLib;
|
||||
|
||||
using NewLife.Caching;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application.Extensions;
|
||||
|
||||
/// <summary>
|
||||
@@ -17,7 +21,7 @@ namespace ThingsGateway.Gateway.Application.Extensions;
|
||||
/// </summary>
|
||||
public interface ReadWriteExpressions
|
||||
{
|
||||
object GetNewValue(dynamic a);
|
||||
object GetNewValue(object a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -25,24 +29,84 @@ public interface ReadWriteExpressions
|
||||
/// </summary>
|
||||
public static class ExpressionEvaluatorExtension
|
||||
{
|
||||
static ExpressionEvaluatorExtension()
|
||||
{
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(30000);
|
||||
//检测缓存
|
||||
try
|
||||
{
|
||||
var data = Instance.GetAll();
|
||||
m_waiterLock.Wait();
|
||||
|
||||
foreach (var item in data)
|
||||
{
|
||||
if (item.Value.ExpiredTime < item.Value.VisitTime + 1800_000)
|
||||
{
|
||||
Instance.Remove(item.Key);
|
||||
item.Value?.Value?.GetType().Assembly.Unload();
|
||||
GC.Collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_waiterLock.Release();
|
||||
}
|
||||
|
||||
await Task.Delay(30000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static MemoryCache Instance { get; set; } = new MemoryCache();
|
||||
private static EasyLock m_waiterLock = new EasyLock();
|
||||
private static string CacheKey = $"{nameof(ExpressionEvaluatorExtension)}-{nameof(GetReadWriteExpressions)}";
|
||||
|
||||
/// <summary>
|
||||
/// 执行脚本获取返回值ReadWriteExpressions
|
||||
/// </summary>
|
||||
public static ReadWriteExpressions GetReadWriteExpressions(string source)
|
||||
{
|
||||
// 生成缓存键
|
||||
var cacheKey = $"{nameof(ExpressionEvaluatorExtension)}-{nameof(GetReadWriteExpressions)}-{source}";
|
||||
var runScript = App.CacheService.GetOrCreate(cacheKey, c =>
|
||||
var field = $"{CacheKey}-{source}";
|
||||
var runScript = Instance.Get<ReadWriteExpressions>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_waiterLock.Wait();
|
||||
{
|
||||
runScript = AddScript(source);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_waiterLock.Release();
|
||||
}
|
||||
}
|
||||
Instance.SetExpire(field, TimeSpan.FromHours(1));
|
||||
|
||||
return runScript;
|
||||
}
|
||||
|
||||
internal static ReadWriteExpressions AddScript(string source)
|
||||
{
|
||||
var field = $"{CacheKey}-{source}";
|
||||
var runScript = Instance.Get<ReadWriteExpressions>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
// 清理输入源字符串
|
||||
source = source.Trim();
|
||||
if (!source.Contains("return"))
|
||||
{
|
||||
source = $"return {source}";//只判断简单脚本中可省略return字符串
|
||||
}
|
||||
|
||||
// 动态加载并执行代码
|
||||
var runScript = CSScript.Evaluator.LoadCode<ReadWriteExpressions>(
|
||||
runScript = CSScript.Evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<ReadWriteExpressions>(
|
||||
$@"
|
||||
using System;
|
||||
using System.Linq;
|
||||
@@ -50,22 +114,24 @@ public static class ExpressionEvaluatorExtension
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
public class Script:ReadWriteExpressions
|
||||
{{
|
||||
public object GetNewValue(dynamic raw)
|
||||
public object GetNewValue(object raw)
|
||||
{{
|
||||
{source};
|
||||
}}
|
||||
}}
|
||||
");
|
||||
return runScript;
|
||||
});
|
||||
GC.Collect();
|
||||
Instance.Set(field, runScript);
|
||||
}
|
||||
return runScript;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算表达式:例如:raw*100,raw为原始值
|
||||
/// 计算表达式:例如:(int)raw*100,raw为原始值
|
||||
/// </summary>
|
||||
public static object GetExpressionsResult(this string expressions, object? rawvalue)
|
||||
{
|
||||
@@ -73,7 +139,6 @@ public static class ExpressionEvaluatorExtension
|
||||
{
|
||||
return rawvalue;
|
||||
}
|
||||
|
||||
var readWriteExpressions = GetReadWriteExpressions(expressions);
|
||||
var value = readWriteExpressions.GetNewValue(rawvalue);
|
||||
return value;
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
/// <summary>
|
||||
/// 后台日志表
|
||||
///</summary>
|
||||
[SugarTable("tg_log_backend", TableDescription = "后台日志表")]
|
||||
[SugarTable("backend_log", TableDescription = "后台日志表")]
|
||||
[Tenant(SqlSugarConst.DB_Log)]
|
||||
public class BackendLog : PrimaryIdEntity
|
||||
{
|
||||
@@ -33,7 +33,7 @@ public class BackendLog : PrimaryIdEntity
|
||||
/// <summary>
|
||||
/// 日志级别
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "日志级别", SqlParameterDbType = typeof(SqlSugar.DbConvert.EnumToStringConvert), IsNullable = false)]
|
||||
[SugarColumn(ColumnDescription = "日志级别", IsNullable = false)]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
public LogLevel LogLevel { get; set; }
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
/// <summary>
|
||||
/// Rpc写入日志
|
||||
///</summary>
|
||||
[SugarTable("rpcLog", TableDescription = "RPC操作日志")]
|
||||
[SugarTable("rpc_log", TableDescription = "RPC操作日志")]
|
||||
[Tenant(SqlSugarConst.DB_Log)]
|
||||
public class RpcLog : PrimaryIdEntity
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ public static class ParallelExtensions
|
||||
{
|
||||
// 创建并行操作的选项对象,设置最大并行度为当前处理器数量的一半
|
||||
ParallelOptions options = new();
|
||||
options.MaxDegreeOfParallelism = Environment.ProcessorCount / 3 == 0 ? 1 : Environment.ProcessorCount / 3;
|
||||
options.MaxDegreeOfParallelism = Environment.ProcessorCount / 2 == 0 ? 1 : Environment.ProcessorCount / 2;
|
||||
// 使用 Parallel.ForEach 执行指定的操作
|
||||
Parallel.ForEach(source, options, (variable) =>
|
||||
{
|
||||
@@ -43,7 +43,7 @@ public static class ParallelExtensions
|
||||
{
|
||||
// 创建并行操作的选项对象,设置最大并行度为当前处理器数量的一半
|
||||
ParallelOptions options = new();
|
||||
options.MaxDegreeOfParallelism = Environment.ProcessorCount / 3 == 0 ? 1 : Environment.ProcessorCount / 3;
|
||||
options.MaxDegreeOfParallelism = Environment.ProcessorCount / 2 == 0 ? 1 : Environment.ProcessorCount / 2;
|
||||
// 使用 Parallel.ForEach 执行指定的操作
|
||||
Parallel.ForEach(source, options, (variable, state, index) =>
|
||||
{
|
||||
@@ -62,7 +62,7 @@ public static class ParallelExtensions
|
||||
{
|
||||
// 创建并行操作的选项对象,设置最大并行度为指定的值
|
||||
var options = new ParallelOptions();
|
||||
options.MaxDegreeOfParallelism = parallelCount / 3 == 0 ? 1 : parallelCount;
|
||||
options.MaxDegreeOfParallelism = parallelCount / 2 == 0 ? 1 : parallelCount;
|
||||
// 使用 Parallel.ForEach 执行指定的操作
|
||||
Parallel.ForEach(source, options, variable =>
|
||||
{
|
||||
@@ -84,7 +84,7 @@ public static class ParallelExtensions
|
||||
// 创建并行操作的选项对象,设置最大并行度和取消标志
|
||||
var options = new ParallelOptions();
|
||||
options.CancellationToken = cancellationToken;
|
||||
options.MaxDegreeOfParallelism = parallelCount / 3 == 0 ? 1 : parallelCount;
|
||||
options.MaxDegreeOfParallelism = parallelCount / 2 == 0 ? 1 : parallelCount;
|
||||
// 使用 Parallel.ForEachAsync 异步执行指定的操作,并返回表示异步操作的任务
|
||||
return Parallel.ForEachAsync(source, options, body);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,6 @@ internal class AdminTaskService : BackgroundService
|
||||
.Select(a => Path.GetFileName(a))
|
||||
.ToArray();
|
||||
|
||||
ChannelConfig channelConfig = AppConfigBase.GetNewDefault<ChannelConfig>();
|
||||
foreach (var item in debugDirs)
|
||||
{
|
||||
if (stoppingToken.IsCancellationRequested)
|
||||
@@ -107,10 +106,7 @@ internal class AdminTaskService : BackgroundService
|
||||
//删除文件夹
|
||||
try
|
||||
{
|
||||
if (!channelConfig.ChannelDatas.Select(a => a.Id.ToString()).Contains(item))
|
||||
{
|
||||
Directory.Delete(debugDir.CombinePathWithOs(item), true);
|
||||
}
|
||||
Directory.Delete(debugDir.CombinePathWithOs(item), true);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
@@ -69,6 +73,8 @@ public class CollectDeviceHostedService : DeviceHostedService
|
||||
_logger.LogInformation(Localizer["DeviceRuntimeGeted"]);
|
||||
var idSet = collectDeviceRunTimes.Where(a => a.RedundantEnable && a.RedundantDeviceId != null).Select(a => a.RedundantDeviceId ?? 0).ToHashSet().ToDictionary(a => a);
|
||||
var result = collectDeviceRunTimes.Where(a => !idSet.ContainsKey(a.Id));
|
||||
|
||||
var scripts = collectDeviceRunTimes.SelectMany(a => a.VariableRunTimes.Where(a => !a.Value.ReadExpressions.IsNullOrWhiteSpace()).Select(b => b.Value.ReadExpressions)).Concat(collectDeviceRunTimes.SelectMany(a => a.VariableRunTimes.Where(a => !a.Value.WriteExpressions.IsNullOrWhiteSpace()).Select(b => b.Value.WriteExpressions))).Distinct().ToList();
|
||||
result.ParallelForEach(collectDeviceRunTime =>
|
||||
{
|
||||
if (!_stoppingToken.IsCancellationRequested)
|
||||
@@ -84,6 +90,20 @@ public class CollectDeviceHostedService : DeviceHostedService
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
scripts.ParallelForEach(script =>
|
||||
{
|
||||
if (!_stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
ExpressionEvaluatorExtension.AddScript(script);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
|
||||
@@ -537,10 +537,10 @@ public abstract class DeviceHostedService : BackgroundService
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!otherstarted)
|
||||
if (HostedServiceUtil.ManagementHostedService.StartCollectDeviceEnable || HostedServiceUtil.ManagementHostedService.StartBusinessDeviceEnable)
|
||||
if (Started != null)
|
||||
await Started.Invoke().ConfigureAwait(false);
|
||||
//if (!otherstarted)
|
||||
if (HostedServiceUtil.ManagementHostedService.StartCollectDeviceEnable || HostedServiceUtil.ManagementHostedService.StartBusinessDeviceEnable)
|
||||
if (Started != null)
|
||||
await Started.Invoke().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -233,12 +233,17 @@ public class ManagementHostedService : BackgroundService
|
||||
online = await tcpDmtpClient.PingAsync(10000).ConfigureAwait(false);
|
||||
if (online)
|
||||
break;
|
||||
else
|
||||
{
|
||||
readErrorCount++;
|
||||
await Task.Delay(Options.Redundancy.SyncInterval);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 捕获异常,增加读取错误计数器
|
||||
readErrorCount++;
|
||||
await Task.Delay(1000);
|
||||
await Task.Delay(Options.Redundancy.SyncInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,6 +290,8 @@ public class ManagementHostedService : BackgroundService
|
||||
|
||||
// 将 IsStart 设置为 true,表示当前设备为启动业务的设备
|
||||
StartCollectDeviceEnable = true;
|
||||
StartBusinessDeviceEnable = true;
|
||||
StopAsync();
|
||||
StartAsync();
|
||||
}
|
||||
}
|
||||
@@ -304,7 +311,9 @@ public class ManagementHostedService : BackgroundService
|
||||
|
||||
// 将 IsStart 设置为 false,表示当前设备为从站,切换到备用状态
|
||||
StartCollectDeviceEnable = false;
|
||||
StartBusinessDeviceEnable = Options?.Redundancy?.Enable == true ? Options?.Redundancy?.IsStartBusinessDevice == true : true;
|
||||
StopAsync();
|
||||
StartAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -332,6 +341,8 @@ public class ManagementHostedService : BackgroundService
|
||||
|
||||
// 将 IsStart 设置为 true,表示当前设备为主站,切换到正常状态
|
||||
StartCollectDeviceEnable = true;
|
||||
StartBusinessDeviceEnable = true;
|
||||
StopAsync();
|
||||
StartAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +326,7 @@
|
||||
"ImportNullError": "Unable to recognize",
|
||||
"NotOther": "Not supporting other channel types",
|
||||
"Connect": "Connect",
|
||||
"Confim": "Confim",
|
||||
"Disconnect": "Disconnect",
|
||||
"Channel": "Channel"
|
||||
},
|
||||
|
||||
@@ -383,6 +383,7 @@
|
||||
|
||||
"NotOther": "不支持其他通道类型",
|
||||
"Connect": "连接",
|
||||
"Confim": "创建",
|
||||
"Disconnect": "断开",
|
||||
"Channel": "通道"
|
||||
},
|
||||
|
||||
@@ -53,7 +53,7 @@ public class VariableMethod
|
||||
/// <param name="value">以,逗号分割的参数</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
public async ValueTask<OperResult> InvokeMethodAsync(object driverBase, string? value = null, CancellationToken cancellationToken = default)
|
||||
public async ValueTask<IOperResult> InvokeMethodAsync(object driverBase, string? value = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -119,8 +119,17 @@ public class VariableMethod
|
||||
{
|
||||
os[i] = cancellationToken;
|
||||
}
|
||||
else if(ps[i].HasDefaultValue)
|
||||
{
|
||||
if (strs.Length <= index)
|
||||
{
|
||||
os[i] = ps[i].DefaultValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strs.Length <= index)
|
||||
break;
|
||||
os[i] = ThingsGatewayStringConverter.Default.Deserialize(null, strs[index], ps[i].ParameterType);
|
||||
index++;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public abstract class BusinessBaseWithCacheAlarmModel<VarModel, DevModel, AlarmM
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddCache(List<CacheDBItem<AlarmModel>> data)
|
||||
{
|
||||
if (data?.Count > 0)
|
||||
if (_businessPropertyWithCache.CacheEnable && data?.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -87,8 +87,22 @@ public abstract class BusinessBaseWithCacheAlarmModel<VarModel, DevModel, AlarmM
|
||||
AddCache(list);
|
||||
}
|
||||
}
|
||||
|
||||
_memoryAlarmModelQueue.Enqueue(data);
|
||||
if (_memoryAlarmModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
lock (_memoryAlarmModelQueue)
|
||||
{
|
||||
if (_memoryAlarmModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
_memoryAlarmModelQueue.Clear();
|
||||
_memoryAlarmModelQueue.Enqueue(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryAlarmModelQueue.Enqueue(data);
|
||||
}
|
||||
}
|
||||
|
||||
private volatile bool LocalDBCacheAlarmModelInited;
|
||||
|
||||
@@ -28,7 +28,7 @@ public abstract class BusinessBaseWithCacheDevModel<VarModel, DevModel> : Busine
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddCache(List<CacheDBItem<DevModel>> data)
|
||||
{
|
||||
if (data?.Count > 0)
|
||||
if (_businessPropertyWithCache.CacheEnable && data?.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -87,8 +87,22 @@ public abstract class BusinessBaseWithCacheDevModel<VarModel, DevModel> : Busine
|
||||
AddCache(list);
|
||||
}
|
||||
}
|
||||
|
||||
_memoryDevModelQueue.Enqueue(data);
|
||||
if (_memoryDevModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
lock (_memoryDevModelQueue)
|
||||
{
|
||||
if (_memoryDevModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
_memoryDevModelQueue.Clear();
|
||||
_memoryDevModelQueue.Enqueue(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryDevModelQueue.Enqueue(data);
|
||||
}
|
||||
}
|
||||
|
||||
private volatile bool LocalDBCacheDevModelInited;
|
||||
|
||||
@@ -34,7 +34,7 @@ public abstract class BusinessBaseWithCacheVarModel<VarModel> : BusinessBase
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddCache(List<CacheDBItem<VarModel>> data)
|
||||
{
|
||||
if (data?.Count > 0)
|
||||
if (_businessPropertyWithCache.CacheEnable && data?.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -91,8 +91,22 @@ public abstract class BusinessBaseWithCacheVarModel<VarModel> : BusinessBase
|
||||
AddCache(list);
|
||||
}
|
||||
}
|
||||
|
||||
_memoryVarModelQueue.Enqueue(data);
|
||||
if (_memoryVarModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
lock (_memoryVarModelQueue)
|
||||
{
|
||||
if (_memoryVarModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
_memoryVarModelQueue.Clear();
|
||||
_memoryVarModelQueue.Enqueue(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryVarModelQueue.Enqueue(data);
|
||||
}
|
||||
}
|
||||
|
||||
private volatile bool LocalDBCacheVarModelInited;
|
||||
|
||||
@@ -93,7 +93,7 @@ public abstract class CollectBase : DriverBase
|
||||
// await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
throw new OperationCanceledException();
|
||||
return new(new OperationCanceledException());
|
||||
// 从协议读取数据
|
||||
var read = await Protocol.ReadAsync(variableSourceRead.RegisterAddress, variableSourceRead.Length, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -103,7 +103,8 @@ public abstract class CollectBase : DriverBase
|
||||
// 如果读取成功且有有效内容,则解析结构化内容
|
||||
if (read.IsSuccess)
|
||||
{
|
||||
variableSourceRead.VariableRunTimes.PraseStructContent(Protocol, read.Content);
|
||||
var prase = variableSourceRead.VariableRunTimes.PraseStructContent(Protocol, read.Content, false);
|
||||
return new OperResult<byte[]>(prase);
|
||||
}
|
||||
|
||||
// 返回读取结果
|
||||
|
||||
@@ -349,7 +349,7 @@ public class PluginService : IPluginService
|
||||
foreach (var propertyInfo in pluginProperties ?? new List<PropertyInfo>())
|
||||
{
|
||||
// 在设备属性列表中查找与当前属性相同名称的属性
|
||||
if (!deviceProperties.TryGetValue(propertyInfo.Name.ToLower(), out var deviceProperty))
|
||||
if (!deviceProperties.TryGetValue(propertyInfo.Name, out var deviceProperty))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -31,11 +31,6 @@ public class Startup : AppStartup
|
||||
.NameMatchingStrategy(NameMatchingStrategy.Flexible)
|
||||
.PreserveReference(true);
|
||||
|
||||
// 配置默认全局映射(忽略大小写敏感)
|
||||
TypeAdapterConfig.GlobalSettings.Default
|
||||
.NameMatchingStrategy(NameMatchingStrategy.IgnoreCase)
|
||||
.PreserveReference(true);
|
||||
|
||||
//运行日志写入数据库配置
|
||||
services.AddDatabaseLogging<BackendLogDatabaseLoggingWriter>(options =>
|
||||
{
|
||||
|
||||
@@ -76,7 +76,13 @@ public partial class AdapterDebugComponent : AdapterDebugBase
|
||||
{
|
||||
try
|
||||
{
|
||||
item.VariableRunTimes.PraseStructContent(Plc, result.Content, exWhenAny: true);
|
||||
var result1 = item.VariableRunTimes.PraseStructContent(Plc, result.Content, exWhenAny: true);
|
||||
if (!result1.IsSuccess)
|
||||
{
|
||||
item.LastErrorMessage = result1.ErrorMessage;
|
||||
item.VariableRunTimes.ForEach(a => a.SetValue(null, isOnline: false));
|
||||
Plc.Logger.Warning(result1.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
<BodyTemplate>
|
||||
<ValidateForm Model="Model" OnValidSubmit="ValidSubmit">
|
||||
|
||||
<EditorForm class="p-2" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=4 LabelWidth=100 Model="Model" >
|
||||
<EditorForm class="p-2" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=3 LabelWidth=100 Model="Model" >
|
||||
|
||||
<FieldItems>
|
||||
|
||||
<EditorItem @bind-Field="@context.ChannelType">
|
||||
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-sm-6 col-md-3">
|
||||
<div class="col-12 col-sm-6 col-md-4">
|
||||
<Select SkipValidate="true" @bind-Value="@value.ChannelType" OnSelectedItemChanged=@((a)=>InvokeAsync(StateHasChanged)) />
|
||||
</div>
|
||||
</EditTemplate>
|
||||
@@ -36,7 +36,8 @@
|
||||
|
||||
</FieldItems>
|
||||
<Buttons>
|
||||
<Button ButtonType="ButtonType.Submit" IsAsync class="mx-2" Color=Color.Primary OnClick="ConnectClick">@Localizer["Connect"]</Button>
|
||||
<Button ButtonType="ButtonType.Submit" IsAsync class="mx-2" Color=Color.Primary OnClick="ConfimClick">@Localizer["Confim"]</Button>
|
||||
<Button IsAsync class="mx-2" Color=Color.Primary OnClick="ConnectClick">@Localizer["Connect"]</Button>
|
||||
<Button IsAsync class="mx-2" Color=Color.Warning OnClick="DisconnectClick">@Localizer["Disconnect"]</Button>
|
||||
</Buttons>
|
||||
</EditorForm>
|
||||
|
||||
@@ -29,6 +29,9 @@ public partial class ChannelDataDebugComponent : ComponentBase
|
||||
[Parameter]
|
||||
public EventCallback<ChannelData> OnConnectClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<ChannelData> OnConfimClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback OnDisConnectClick { get; set; }
|
||||
|
||||
@@ -117,28 +120,29 @@ public partial class ChannelDataDebugComponent : ComponentBase
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConfimClick()
|
||||
{
|
||||
try
|
||||
{
|
||||
ChannelData.CreateChannel(Model);
|
||||
if (OnConfimClick.HasDelegate)
|
||||
await OnConfimClick.InvokeAsync(Model);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Model.Channel?.Logger.Exception(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectClick()
|
||||
{
|
||||
ChannelData.CreateChannel(Model);
|
||||
if (Model != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (OnConnectClick.HasDelegate)
|
||||
await OnConnectClick.InvokeAsync(Model);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
try
|
||||
{
|
||||
ChannelData.CreateChannel(Model);
|
||||
if (Model.Channel != null)
|
||||
if (OnConnectClick.HasDelegate)
|
||||
await OnConnectClick.InvokeAsync(Model);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Model.Channel.Logger.Exception(ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -34,6 +34,13 @@
|
||||
"ChoiceModule": "Switch Module",
|
||||
"GiteePostTitleText": "Code Commit Push Notification",
|
||||
|
||||
"LoginErrorh1": "Login Error",
|
||||
"LoginSuccessh1": "Login Success",
|
||||
"LoginSuccessc1": "Redirecting to the page",
|
||||
"LoginErrorh2": "Login Failed",
|
||||
"LoginErrorc2": "Please contact the administrator!",
|
||||
"Logout": "Logout",
|
||||
|
||||
"系统首页": "Home",
|
||||
"权限管理": "Permission",
|
||||
"用户管理": "User",
|
||||
|
||||
@@ -32,6 +32,13 @@
|
||||
"ChoiceModule": "切换模块",
|
||||
"GiteePostTitleText": "代码提交推送通知",
|
||||
|
||||
"LoginErrorh1": "登录异常",
|
||||
"LoginSuccessh1": "登录成功",
|
||||
"LoginSuccessc1": "即将跳转页面",
|
||||
"LoginErrorh2": "登录失败",
|
||||
"LoginErrorc2": "请联系管理员!",
|
||||
"Logout": "注销",
|
||||
|
||||
"系统首页": "系统首页",
|
||||
"权限管理": "权限管理",
|
||||
"用户管理": "用户管理",
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
@* 切换模块 *@
|
||||
<a @onclick="ChoiceModule"><i class="fas fa-arrow-right-arrow-left me-2" />@Localizer["ChoiceModule"]</a>
|
||||
<a @onclick="@OnUserInfoDialog"><i class="fa-solid fa-suitcase me-2"></i>@Localizer["UserCenter"]</a>
|
||||
<LogoutLink Url=@("api/auth/logout?returnUrl="+NavigationManager.ToBaseRelativePath(NavigationManager.Uri)) />
|
||||
<a @onclick="@LogoutAsync"><i class="fa-solid fa-key me-2"></i>@Localizer["Logout"]</a>
|
||||
|
||||
</LinkTemplate>
|
||||
</Logout>
|
||||
@@ -68,11 +68,10 @@
|
||||
|
||||
</Main>
|
||||
<NotAuthorized>
|
||||
<Redirect />
|
||||
<Redirect/>
|
||||
</NotAuthorized>
|
||||
</Layout>
|
||||
|
||||
|
||||
<QuickActions></QuickActions>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,11 +8,16 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using SqlSugar;
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Admin.Razor;
|
||||
using ThingsGateway.Core;
|
||||
using ThingsGateway.Core.Json.Extension;
|
||||
using ThingsGateway.Razor;
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
@@ -110,6 +115,44 @@ public partial class MainLayout : IDisposable
|
||||
|
||||
#endregion 切换模块
|
||||
|
||||
#region 注销
|
||||
[Inject]
|
||||
AjaxService AjaxService { get; set; }
|
||||
private async Task LogoutAsync()
|
||||
{
|
||||
var ajaxOption = new AjaxOption
|
||||
{
|
||||
Url = "/api/auth/logout",
|
||||
Method = "POST",
|
||||
};
|
||||
using var str = await AjaxService.InvokeAsync(ajaxOption);
|
||||
if (str != null)
|
||||
{
|
||||
var ret = str.RootElement.GetRawText().FromSystemTextJsonString<UnifyResult<object>>();
|
||||
if (ret.Code != 200)
|
||||
{
|
||||
await ToastService.Error(Localizer["LoginErrorh1"], $"{ret.Msg}");
|
||||
}
|
||||
else
|
||||
{
|
||||
await ToastService.Information(Localizer["LoginSuccessh1"], Localizer["LoginSuccessc1"]);
|
||||
await Task.Delay(1000);
|
||||
var url = QueryHelpers.AddQueryString(CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
|
||||
{
|
||||
["ReturnUrl"] = NavigationManager.ToBaseRelativePath(NavigationManager.Uri)
|
||||
});
|
||||
await AjaxService.Goto(url);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ToastService.Error(Localizer["LoginErrorh2"], Localizer["LoginErrorc2"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task ShowAbout()
|
||||
{
|
||||
DialogOption? op = null;
|
||||
|
||||
@@ -69,10 +69,10 @@ public partial class VariableEditComponent
|
||||
{
|
||||
try
|
||||
{
|
||||
if (BusinessDeviceDict.TryGetValue(selectedItem.Value.ToLong(), out var device))
|
||||
if (CollectDeviceDict.TryGetValue(selectedItem.Value.ToLong(), out var device))
|
||||
{
|
||||
var data = PluginService.GetDriverMethodInfos(device.PluginName);
|
||||
OtherMethods = data.Select(a => new SelectedItem(a.Name, a.Description));
|
||||
OtherMethods = data.Select(a => new SelectedItem(a.Name, a.Description)).Append(new SelectedItem(string.Empty,"none"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-2 mx-1 form-inline">
|
||||
|
||||
<div class="col-12 col-md-6 sh">
|
||||
@@ -63,7 +63,7 @@
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6 sh" >
|
||||
<div class="col-12 col-md-6 sh">
|
||||
|
||||
<Card IsShadow=true class="m-2 flex-fill">
|
||||
<HeaderTemplate>
|
||||
@@ -78,15 +78,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
|
||||
<HeaderTemplate>
|
||||
@Localizer["HardwareInfoChart"]
|
||||
</HeaderTemplate>
|
||||
|
||||
<BodyTemplate>
|
||||
<Chart @ref=CPULineChart OnInitAsync="OnCPUInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="()=>{chartInit=true;return Task.CompletedTask;}" />
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
<div class="row g-2 mx-1 form-inline">
|
||||
<div class="col-12 col-md-12 sh">
|
||||
<Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
|
||||
<HeaderTemplate>
|
||||
@Localizer["HardwareInfoChart"]
|
||||
</HeaderTemplate>
|
||||
|
||||
<BodyTemplate>
|
||||
<Chart @ref=CPULineChart OnInitAsync="OnCPUInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="()=>{chartInit=true;return Task.CompletedTask;}" />
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -118,6 +118,9 @@ public class MemoryCache : Cache
|
||||
return (T?)ci.Visit();
|
||||
}
|
||||
|
||||
/// <summary>返回全部</summary>
|
||||
public IReadOnlyDictionary<string, CacheItem> GetAll() => _cache;
|
||||
|
||||
#endregion 方法
|
||||
|
||||
#region 基本操作
|
||||
@@ -456,7 +459,7 @@ public class MemoryCache : Cache
|
||||
#region 缓存项
|
||||
|
||||
/// <summary>缓存项</summary>
|
||||
protected class CacheItem
|
||||
public class CacheItem
|
||||
{
|
||||
private Object? _Value;
|
||||
|
||||
@@ -464,7 +467,7 @@ public class MemoryCache : Cache
|
||||
public Object? Value { get => _Value; set => _Value = value; }
|
||||
|
||||
/// <summary>过期时间</summary>
|
||||
public Int64 ExpiredTime { get; set; }
|
||||
public Int64 ExpiredTime { get;internal set; }
|
||||
|
||||
/// <summary>是否过期</summary>
|
||||
public Boolean Expired => ExpiredTime <= Runtime.TickCount64;
|
||||
@@ -480,7 +483,7 @@ public class MemoryCache : Cache
|
||||
/// <summary>设置数值和过期时间</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="expire">过期时间,秒</param>
|
||||
public void Set(Object? value, Int32 expire)
|
||||
internal void Set(Object? value, Int32 expire)
|
||||
{
|
||||
Value = value;
|
||||
|
||||
@@ -493,7 +496,7 @@ public class MemoryCache : Cache
|
||||
|
||||
/// <summary>更新访问时间并返回数值</summary>
|
||||
/// <returns></returns>
|
||||
public Object? Visit()
|
||||
internal Object? Visit()
|
||||
{
|
||||
VisitTime = Runtime.TickCount64;
|
||||
return Value;
|
||||
@@ -502,7 +505,7 @@ public class MemoryCache : Cache
|
||||
/// <summary>递增</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public Int64 Inc(Int64 value)
|
||||
internal Int64 Inc(Int64 value)
|
||||
{
|
||||
// 原子操作
|
||||
Int64 newValue;
|
||||
@@ -521,7 +524,7 @@ public class MemoryCache : Cache
|
||||
/// <summary>递增</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public Double Inc(Double value)
|
||||
internal Double Inc(Double value)
|
||||
{
|
||||
// 原子操作
|
||||
Double newValue;
|
||||
@@ -540,7 +543,7 @@ public class MemoryCache : Cache
|
||||
/// <summary>递减</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public Int64 Dec(Int64 value)
|
||||
internal Int64 Dec(Int64 value)
|
||||
{
|
||||
// 原子操作
|
||||
Int64 newValue;
|
||||
@@ -559,7 +562,7 @@ public class MemoryCache : Cache
|
||||
/// <summary>递减</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public Double Dec(Double value)
|
||||
internal Double Dec(Double value)
|
||||
{
|
||||
// 原子操作
|
||||
Double newValue;
|
||||
@@ -621,6 +624,7 @@ public class MemoryCache : Cache
|
||||
ss.Add(item.Key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 如果满了,删除前面
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor" Version="8.6.1" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="8.6.2" />
|
||||
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="8.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||
|
||||
#构建:docker build -t diego2098/thingsgateway:latest .
|
||||
|
||||
#构建:docker build -t registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway:latest .
|
||||
#拉取:docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway
|
||||
#推送:docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway
|
||||
|
||||
#aspnetcore8.0环境
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||
@@ -9,9 +10,6 @@ COPY . /app
|
||||
WORKDIR /app
|
||||
#默认web
|
||||
EXPOSE 5000
|
||||
#图像库
|
||||
RUN echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ sid main contrib non-free" > /etc/apt/sources.list
|
||||
RUN apt-get update && apt-get -y install libfontconfig1
|
||||
|
||||
# 添加时区环境变量,亚洲,上海
|
||||
ENV TimeZone=Asia/Shanghai
|
||||
|
||||
@@ -1,29 +1,15 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:29523",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:7100",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@ public class BlazorAuthenticationStateProvider : AppAuthorizeHandler
|
||||
private readonly ISysUserService _sysUserService;
|
||||
private readonly ISysRoleService _sysRoleService;
|
||||
private readonly ISysDictService _sysDictService;
|
||||
private readonly IVerificatInfoCacheService _verificatInfoCacheService;
|
||||
private readonly IVerificatInfoService _verificatInfoService;
|
||||
|
||||
public BlazorAuthenticationStateProvider(IVerificatInfoCacheService verificatInfoCacheService, ISysUserService sysUserService, ISysRoleService sysRoleService, ISysDictService sysDictService)
|
||||
public BlazorAuthenticationStateProvider(IVerificatInfoService verificatInfoService, ISysUserService sysUserService, ISysRoleService sysRoleService, ISysDictService sysDictService)
|
||||
{
|
||||
_sysUserService = sysUserService;
|
||||
_sysRoleService = sysRoleService;
|
||||
_sysDictService = sysDictService;
|
||||
_verificatInfoCacheService = verificatInfoCacheService;
|
||||
_verificatInfoService = verificatInfoService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -189,17 +189,18 @@ public class BlazorAuthenticationStateProvider : AppAuthorizeHandler
|
||||
DefaultHttpContext currentHttpContext = context.GetCurrentHttpContext();
|
||||
var userId = context.User.Claims.FirstOrDefault(it => it.Type == ClaimConst.UserId)?.Value?.ToLong();
|
||||
var verificatId = context.User.Claims.FirstOrDefault(it => it.Type == ClaimConst.VerificatId)?.Value?.ToLong();
|
||||
var expire = (await _sysDictService.GetAppConfigAsync()).LoginPolicy.VerificatExpireTime;
|
||||
if (currentHttpContext == null)
|
||||
{
|
||||
var verificatInfos = userId != null ? _verificatInfoCacheService.HashGetOne(userId.Value) : null;//获取token信息
|
||||
var verificatInfo = userId != null ? _verificatInfoService.GetOne(verificatId ?? 0) : null;//获取token信息
|
||||
|
||||
if (verificatInfos == null) //如果还是空
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var verificatInfo = verificatInfos.FirstOrDefault(it => it.Id == verificatId); //获取cache中token值是当前token的对象
|
||||
if (verificatInfo != null)
|
||||
{
|
||||
if (verificatInfo.VerificatTimeout < DateTime.Now.AddMinutes(5))
|
||||
{
|
||||
verificatInfo.VerificatTimeout = DateTime.Now.AddMinutes(30); //新的过期时间
|
||||
_verificatInfoService.Update(verificatInfo); //更新tokne信息到cache
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -209,21 +210,18 @@ public class BlazorAuthenticationStateProvider : AppAuthorizeHandler
|
||||
}
|
||||
else
|
||||
{
|
||||
var expire = (await _sysDictService.GetAppConfigAsync()).LoginPolicy.VerificatExpireTime;
|
||||
if (JWTEncryption.AutoRefreshToken(context, currentHttpContext, expire, expire * 2))
|
||||
{
|
||||
//var token = JWTEncryption.GetJwtBearerToken(currentHttpContext); //获取当前token
|
||||
|
||||
var verificatInfos = userId != null ? _verificatInfoCacheService.HashGetOne(userId.Value) : null;//获取token信息
|
||||
if (verificatInfos == null) //如果还是空
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var verificatInfo = verificatInfos.FirstOrDefault(it => it.Id == verificatId); //获取cache中token值是当前token的对象
|
||||
var verificatInfo = userId != null ? _verificatInfoService.GetOne(verificatId ?? 0) : null;//获取token信息
|
||||
if (verificatInfo != null)
|
||||
{
|
||||
verificatInfo.VerificatTimeout = DateTime.Now.AddMinutes(expire); //新的过期时间
|
||||
_verificatInfoCacheService.HashAdd(userId!.Value, verificatInfos); //更新tokne信息到cache
|
||||
if (verificatInfo.VerificatTimeout < DateTime.Now.AddMinutes(5))
|
||||
{
|
||||
verificatInfo.VerificatTimeout = DateTime.Now.AddMinutes(verificatInfo.Expire); //新的过期时间
|
||||
_verificatInfoService.Update(verificatInfo); //更新tokne信息到cache
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -68,7 +68,7 @@ public class Startup : AppStartup
|
||||
var authenticationBuilder = services.AddAuthentication(nameof(ThingsGateway)).AddCookie(nameof(ThingsGateway), a =>
|
||||
{
|
||||
a.AccessDeniedPath = "/Account/AccessDenied/";
|
||||
a.LogoutPath = "/api/auth/logout";
|
||||
a.LogoutPath = "/Account/Logout/";
|
||||
a.LoginPath = "/Account/Login/";
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
//"urls": "http://*:7100",
|
||||
"urls": "http://*:5000",
|
||||
|
||||
"DetailedErrors": true,
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
//"urls": "http://*:7100",
|
||||
"urls": "http://*:5000",
|
||||
|
||||
"ConfigurationScanDirectories": [ "" ],
|
||||
//冗余配置
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>6.0.2.27</Version>
|
||||
<Version>6.0.3.12</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -17,8 +17,8 @@ internal class ModbusMasterTest
|
||||
{
|
||||
private static ModbusMaster GetMaster()
|
||||
{
|
||||
var clientConfig = new TouchSocketConfig();
|
||||
ConsoleLogger.Default.LogLevel = LogLevel.Trace;
|
||||
var clientConfig = new TouchSocketConfig();
|
||||
clientConfig.ConfigureContainer(a => a.AddConsoleLogger());
|
||||
//创建通道,也可以通过TouchSocketConfig.GetChannel扩展获取
|
||||
//var clientChannel = clientConfig.GetTcpServiceWithBindIPHost("tcp://127.0.0.1:502");
|
||||
@@ -38,7 +38,7 @@ internal class ModbusMasterTest
|
||||
{
|
||||
using ModbusMaster modbusMaster = GetMaster();
|
||||
//modbusMaster.HeartbeatHexString = "ccccdddd";//心跳
|
||||
await modbusMaster.Channel.ConnectAsync();
|
||||
await modbusMaster.ConnectAsync();
|
||||
//Console.WriteLine("回车后读取注册包为abcd的客户端");
|
||||
Console.ReadLine();
|
||||
var data = await modbusMaster.ReadInt16Async("40001;id=abcd");//寄存器;{id=注册包}
|
||||
@@ -53,7 +53,7 @@ internal class ModbusMasterTest
|
||||
, 10, 10000
|
||||
);
|
||||
|
||||
Console.WriteLine(waitResult.ToJsonString());
|
||||
Console.WriteLine(waitResult.ToJsonNetString());
|
||||
//构造实体类对象,传入协议对象与连读打包的最大数量
|
||||
ModbusVariable modbusVariable = new(modbusMaster, 100);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ThingsGateway.Foundation.Variable" Version="8.1" />
|
||||
<PackageReference Include="ThingsGateway.Foundation.Variable" Version="8.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ internal class ModbusHelper
|
||||
{
|
||||
Content = new()
|
||||
{
|
||||
Content = response.ToArray(response.Position + 3),
|
||||
Content = response.ToArray(response.Position + 3, response[response.Position + 2]),
|
||||
FilterResult = FilterResult.Success
|
||||
}
|
||||
};
|
||||
@@ -141,7 +141,7 @@ internal class ModbusHelper
|
||||
{
|
||||
Content = new()
|
||||
{
|
||||
Content = response.ToArray(response.Position + 3),
|
||||
Content = response.ToArray(response.Position + 3, response[response.Position + 2]),
|
||||
FilterResult = FilterResult.Success
|
||||
}
|
||||
};
|
||||
@@ -167,7 +167,7 @@ internal class ModbusHelper
|
||||
{
|
||||
//crc校验
|
||||
var dataLen = response.Length;
|
||||
var crc = CRC16Utils.CRC16Only(response.AsSegment().Array, 0, dataLen - 2);
|
||||
var crc = CRC16Utils.Crc16Only(response.AsSegment().Array, 0, dataLen - 2);
|
||||
if ((response[dataLen - 2] != crc[0] || response[dataLen - 1] != crc[1]))
|
||||
return new OperResult($"{ModbusResource.Localizer["CrcError"]} {DataTransUtil.ByteToHexString(response.Span, ' ')}")
|
||||
{
|
||||
@@ -202,7 +202,7 @@ internal class ModbusHelper
|
||||
|
||||
public static byte[] AddCrc(ISendMessage item)
|
||||
{
|
||||
var crc = CRC16Utils.CRC16(item.SendBytes);
|
||||
var crc = CRC16Utils.Crc16(item.SendBytes);
|
||||
return crc;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,13 +30,16 @@ internal class ModbusTcpDataHandleAdapter : ReadWriteDevicesSingleStreamDataHand
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusTcpMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
byteBlock.Position = 6;
|
||||
var pos = byteBlock.Position;
|
||||
byteBlock.Position = byteBlock.Position + 6;
|
||||
var result = ModbusHelper.GetModbusData(null, byteBlock);
|
||||
|
||||
request.OperCode = result.OperCode;
|
||||
request.ErrorMessage = result.ErrorMessage;
|
||||
|
||||
byteBlock.Position = 0;
|
||||
byteBlock.Position = pos;
|
||||
request.Sign = byteBlock.ReadUInt16(EndianType.Big);
|
||||
|
||||
return result.Content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +30,13 @@ internal class ModbusUdpDataHandleAdapter : ReadWriteDevicesUdpDataHandleAdapter
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusTcpMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
byteBlock.Position = 6;
|
||||
var pos = byteBlock.Position;
|
||||
byteBlock.Position = byteBlock.Position + 6;
|
||||
var result = ModbusHelper.GetModbusData(null, byteBlock);
|
||||
request.OperCode = result.OperCode;
|
||||
request.ErrorMessage = result.ErrorMessage;
|
||||
|
||||
byteBlock.Position = 0;
|
||||
byteBlock.Position = pos;
|
||||
request.Sign = byteBlock.ReadUInt16(EndianType.Big);
|
||||
return result.Content;
|
||||
}
|
||||
|
||||
@@ -24,4 +24,9 @@ internal interface IModbusServerMessage : IResultMessage
|
||||
/// 当前关联的地址
|
||||
/// </summary>
|
||||
ModbusAddress ModbusAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前关联的字节数组
|
||||
/// </summary>
|
||||
ReadOnlyMemory<byte> Bytes { get; set; }
|
||||
}
|
||||
|
||||
@@ -28,13 +28,15 @@ internal class ModbusRtuOverUdpServerDataHandleAdapter : ReadWriteDevicesUdpData
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusRtuServerMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
var pos = byteBlock.Position;
|
||||
var result = ModbusHelper.CheckCrc(byteBlock);
|
||||
request.OperCode = result.OperCode;
|
||||
request.ErrorMessage = result.ErrorMessage;
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
byteBlock.Position = 0;
|
||||
byteBlock.Position = pos;
|
||||
var bytes = ModbusHelper.ModbusServerAnalysisAddressValue(request, byteBlock);
|
||||
request.Bytes = byteBlock.AsSegment(pos, request.HeadBytesLength + request.BodyLength);
|
||||
return new AdapterResult() { FilterResult = FilterResult.Success, Content = bytes };
|
||||
}
|
||||
else
|
||||
|
||||
@@ -26,13 +26,15 @@ internal class ModbusRtuServerDataHandleAdapter : ReadWriteDevicesSingleStreamDa
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusRtuServerMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
var pos = byteBlock.Position;
|
||||
var result = ModbusHelper.CheckCrc(byteBlock);
|
||||
request.OperCode = result.OperCode;
|
||||
request.ErrorMessage = result.ErrorMessage;
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
byteBlock.Position = 0;
|
||||
byteBlock.Position = pos;
|
||||
var bytes = ModbusHelper.ModbusServerAnalysisAddressValue(request, byteBlock);
|
||||
request.Bytes = byteBlock.AsSegment(pos, request.HeadBytesLength + request.BodyLength);
|
||||
return new AdapterResult() { FilterResult = FilterResult.Success, Content = bytes };
|
||||
}
|
||||
else
|
||||
|
||||
@@ -18,6 +18,11 @@ internal class ModbusRtuServerMessage : MessageBase, IResultMessage, IModbusServ
|
||||
/// <inheritdoc/>
|
||||
public ModbusAddress ModbusAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前关联的字节数组
|
||||
/// </summary>
|
||||
public ReadOnlyMemory<byte> Bytes { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Length { get; set; }
|
||||
|
||||
|
||||
@@ -16,8 +16,10 @@ internal class ModbusTcpServerDataHandleAdapter : ReadWriteDevicesSingleStreamDa
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusTcpServerMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
byteBlock.Position = 6;
|
||||
var pos = byteBlock.Position;
|
||||
byteBlock.Position = byteBlock.Position + 6;
|
||||
var bytes = ModbusHelper.ModbusServerAnalysisAddressValue(request, byteBlock);
|
||||
request.Bytes = byteBlock.AsSegment(pos, request.HeadBytesLength + request.BodyLength);
|
||||
return new AdapterResult() { FilterResult = FilterResult.Success, Content = bytes };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ internal class ModbusTcpServerMessage : MessageBase, IResultMessage, IModbusServ
|
||||
/// </summary>
|
||||
public ModbusAddress ModbusAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前关联的字节数组
|
||||
/// </summary>
|
||||
public ReadOnlyMemory<byte> Bytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前读写的数据长度
|
||||
/// </summary>
|
||||
|
||||
@@ -15,16 +15,13 @@ namespace ThingsGateway.Foundation.Modbus;
|
||||
/// </summary>
|
||||
internal class ModbusUdpServerDataHandleAdapter : ReadWriteDevicesUdpDataHandleAdapter<ModbusTcpServerMessage>
|
||||
{
|
||||
public override byte[] PackCommand(ISendMessage item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusTcpServerMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
byteBlock.Position = 6;
|
||||
var pos = byteBlock.Position;
|
||||
byteBlock.Position = byteBlock.Position + 6;
|
||||
var bytes = ModbusHelper.ModbusServerAnalysisAddressValue(request, byteBlock);
|
||||
request.Bytes = byteBlock.AsSegment(pos, request.HeadBytesLength + request.BodyLength);
|
||||
return new AdapterResult() { FilterResult = FilterResult.Success, Content = bytes };
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user