Compare commits

...

37 Commits

Author SHA1 Message Date
Diego
d6b0a9314c refactor: 初始化缓存编译读写表达式 2024-06-17 13:49:45 +08:00
Diego
83bc07c3ff release:6.0.3.11
修改默认启动端口
2024-06-17 10:32:05 +08:00
Diego2098
6ca680cd41 refactor: 数据库表名风格统一 2024-06-16 13:59:17 +08:00
Diego2098
2b533dcb2b fix: s7驱动多语言文本错误 2024-06-15 20:18:37 +08:00
Diego2098
13a5a09f98 refactor: 优化modbusSlave 2024-06-15 19:09:18 +08:00
Diego
75ff7ebdbe other:更新调试工具 2024-06-14 16:01:42 +08:00
Diego
6d8f3a1fc8 release:6.0.3.9 2024-06-14 14:14:39 +08:00
Diego
a11b411912 fix: 修复上个提交导致的表达式空指针错误 2024-06-14 09:28:18 +08:00
Diego
2dba817c69 feat: 登录页面添加版本提示 2024-06-13 16:55:54 +08:00
Diego
d4df9a545f release:6.0.3.8 2024-06-13 14:22:36 +08:00
Diego
cae21391e5 release:6.0.3.8 2024-06-13 14:20:53 +08:00
Diego
573b22d2c2 refactor: 优化CSScript编译 2024-06-13 14:20:30 +08:00
Diego
14d4112a9d fix: 命名空间错误 2024-06-13 13:47:18 +08:00
Diego
28aa9db9c5 更新依赖 2024-06-13 13:40:33 +08:00
Diego
d50c349e00 release:6.0.3.7
破坏性更改

动态编译脚本表达式,传入值不再是dynamic类型,而是object,注意类型转换
2024-06-13 13:38:17 +08:00
Diego
905362c59c release:6.0.3.6 2024-06-13 10:23:36 +08:00
Diego
97e7d44310 fix: 特殊方法选择错误 2024-06-12 16:29:04 +08:00
Diego
bb71a1ef23 feat: tcpservice支持多个ip绑定,以分号间隔 2024-06-12 14:29:14 +08:00
youthalan
de2771d008 !31 添加ShowExtendEditButtonCallback等
Merge pull request !31 from youthalan/master
2024-06-12 02:59:09 +00:00
Diego
02de34b65a refactor: crc方法修改 2024-06-11 19:03:19 +08:00
youthalan
c6e14889f2 !30 添加SetRowClassFormatter属性等
Merge pull request !30 from youthalan/master
2024-06-11 08:07:06 +00:00
Diego
f843732790 other: dockerfile 2024-06-11 15:50:52 +08:00
Diego
80e7ff0fc1 release:6.0.3.5 2024-06-11 15:47:58 +08:00
Diego
2964db6bdd fix: modbusUdp已发送数组获取失败 2024-06-11 15:47:36 +08:00
Diego
5fbd94ec74 refactor: 更新依赖 2024-06-11 15:37:50 +08:00
Diego
1d8d31d4f0 other: 更新docker地址 2024-06-11 12:53:08 +08:00
Diego
707c3eed99 release:6.0.3.3
refactor: 优化错误提示
2024-06-11 12:24:17 +08:00
Diego2098
e33a81ad36 refactor: 优化错误提示 2024-06-10 22:53:48 +08:00
Diego2098
0f7d216379 refactor: 优化错误提示 2024-06-10 22:47:54 +08:00
Diego2098
f0052e97a6 refactor: 修改注销方式 2024-06-10 21:05:59 +08:00
Diego
5c6076d48d refactor: 修改token缓存服务 2024-06-09 19:49:29 +08:00
Diego
883bd31249 refactor: 优化debug软件体验 2024-06-09 17:55:57 +08:00
Diego
d7b5fde3c7 fix: 取消mapster映射忽略大小写,这会导致字典键错误 2024-06-09 17:16:56 +08:00
Diego
f4273efef0 fix: 冗余心跳错误 2024-06-09 14:12:39 +08:00
Diego
471dc966d9 fix: 冗余心跳错误 2024-06-09 13:52:12 +08:00
Diego
50368654a4 build: Photino修改为net6 2024-06-09 13:34:01 +08:00
Diego
b1e1cc04ce refactor: dockerfile 2024-06-09 13:18:49 +08:00
130 changed files with 1517 additions and 1062 deletions

View File

@@ -55,7 +55,7 @@ Password: **111111**
## Docker
```shell
docker pull diego2098/thingsgateway:latest
docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway
```


View File

@@ -53,7 +53,7 @@
## Docker
```shell
docker pull diego2098/thingsgateway:latest
docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway
```

View File

@@ -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 `变量地址通用说明`

View File

@@ -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警告(波浪线)

View File

@@ -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)
{

View File

@@ -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
}));
}
}

View File

@@ -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
}));
}
}

View File

@@ -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)

View File

@@ -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
{

View File

@@ -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,
});
}

View File

@@ -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,
});//通知用户下线
}

View File

@@ -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
}
}

View File

@@ -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,
});//通知用户下线
}

View File

@@ -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,
});//通知用户下线
}

View File

@@ -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);
}

View File

@@ -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; }
}

View File

@@ -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);
}

View File

@@ -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; }
}

View File

@@ -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();
}
}

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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>
/// 内容

View File

@@ -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!">

View File

@@ -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; }

View File

@@ -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>

View File

@@ -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; }

View File

@@ -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">

View File

@@ -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;

View File

@@ -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>

View File

@@ -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;

View File

@@ -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",

View File

@@ -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": "注销",
"系统首页": "系统首页",
"权限管理": "权限管理",
"用户管理": "用户管理",

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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
{

View File

@@ -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"))
{
}
}

View File

@@ -103,6 +103,7 @@ public class ChannelData
{
if (channelData.Channel != null)
{
channelData.Channel.Close();
channelData.Channel.SafeDispose();
}
channelData.TouchSocketConfig?.Dispose();

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);//如果本插件无法处理当前数据,请将数据转至下一个插件。

View File

@@ -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);//如果本插件无法处理当前数据,请将数据转至下一个插件。
}
}

View File

@@ -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
{

View File

@@ -8,12 +8,14 @@
// QQ群605534569
//------------------------------------------------------------------------------
using TouchSocket.Resources;
namespace ThingsGateway.Foundation;
/// <summary>
/// 协议基类
/// </summary>
public static class ProtocolBaseExtension
public static partial class ProtocolBaseExtension
{
#region

View File

@@ -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));
}
}
}
}

View File

@@ -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/>

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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)
};
}
}

View File

@@ -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>

View File

@@ -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*100raw为原始值
/// 计算表达式:例如:(int)raw*100raw为原始值
/// </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;

View File

@@ -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; }

View File

@@ -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
{

View File

@@ -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);
}

View File

@@ -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 { }
}

View File

@@ -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++)
{

View File

@@ -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
{

View File

@@ -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();
}
}

View File

@@ -326,6 +326,7 @@
"ImportNullError": "Unable to recognize",
"NotOther": "Not supporting other channel types",
"Connect": "Connect",
"Confim": "Confim",
"Disconnect": "Disconnect",
"Channel": "Channel"
},

View File

@@ -383,6 +383,7 @@
"NotOther": "不支持其他通道类型",
"Connect": "连接",
"Confim": "创建",
"Disconnect": "断开",
"Channel": "通道"
},

View File

@@ -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++;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
// 返回读取结果

View File

@@ -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;
}

View File

@@ -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 =>
{

View File

@@ -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)
{

View File

@@ -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>

View File

@@ -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)
{

View File

@@ -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",

View File

@@ -32,6 +32,13 @@
"ChoiceModule": "切换模块",
"GiteePostTitleText": "代码提交推送通知",
"LoginErrorh1": "登录异常",
"LoginSuccessh1": "登录成功",
"LoginSuccessc1": "即将跳转页面",
"LoginErrorh2": "登录失败",
"LoginErrorc2": "请联系管理员!",
"Logout": "注销",
"系统首页": "系统首页",
"权限管理": "权限管理",
"用户管理": "用户管理",

View File

@@ -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>

View File

@@ -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;

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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);
}
}
}
// 如果满了,删除前面

View File

@@ -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>

View File

@@ -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

View File

@@ -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"
}
}
}
}
}

View File

@@ -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

View File

@@ -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/";
});

View File

@@ -1,5 +1,5 @@
{
//"urls": "http://*:7100",
"urls": "http://*:5000",
"DetailedErrors": true,

View File

@@ -1,5 +1,5 @@
{
//"urls": "http://*:7100",
"urls": "http://*:5000",
"ConfigurationScanDirectories": [ "" ],
//冗余配置

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>6.0.2.27</Version>
<Version>6.0.3.12</Version>
</PropertyGroup>
<ItemGroup>

View File

@@ -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);

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -24,4 +24,9 @@ internal interface IModbusServerMessage : IResultMessage
/// 当前关联的地址
/// </summary>
ModbusAddress ModbusAddress { get; set; }
/// <summary>
/// 当前关联的字节数组
/// </summary>
ReadOnlyMemory<byte> Bytes { get; set; }
}

View File

@@ -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

View File

@@ -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

View File

@@ -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; }

View File

@@ -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 };
}
}

View File

@@ -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>

View File

@@ -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