Compare commits

...

29 Commits

Author SHA1 Message Date
2248356998 qq.com
20a2e3ff8e 示例 2025-08-25 20:39:26 +08:00
Diego
61a973b1b5 !73 10.11.10 2025-08-25 12:21:12 +00:00
2248356998 qq.com
cbd72e2081 添加文本日志配置json文件 2025-08-22 16:17:25 +08:00
2248356998 qq.com
4e0377b20c 添加文本日志配置json文件 2025-08-22 16:15:54 +08:00
2248356998 qq.com
fd318d3cdc 移除警告项 2025-08-22 16:12:14 +08:00
2248356998 qq.com
515bdb9700 10.11.7 2025-08-21 23:35:12 +08:00
2248356998 qq.com
46c1780017 10.11.6 2025-08-21 22:40:53 +08:00
2248356998 qq.com
fe78a4c3ca 更新依赖库 2025-08-21 22:05:30 +08:00
2248356998 qq.com
2d7effadf9 10.11.4 2025-08-21 21:13:18 +08:00
Diego
346c560f8b !72 更新依赖项 2025-08-21 11:46:58 +00:00
Diego
8e3bd89f61 修改编译项 2025-08-18 17:30:34 +08:00
Diego
6da142d080 10.10.23 2025-08-18 17:03:40 +08:00
2248356998 qq.com
ff7d029e6f 更新依赖 2025-08-14 20:25:00 +08:00
2248356998 qq.com
21b4695683 10.10.19 2025-08-14 05:47:54 +08:00
Diego
02ad494a26 !71 适配远程客户端 2025-08-12 16:00:53 +00:00
2248356998 qq.com
280366e1b2 添加写优先选项,默认false 2025-08-09 13:07:08 +08:00
2248356998 qq.com
6660ce3e34 适配远程管理客户端 2025-08-08 18:01:24 +08:00
2248356998 qq.com
7499162c1a 适配远程管理客户端 2025-08-08 02:51:42 +08:00
2248356998 qq.com
40208a5cd6 适配远程网关管理客户端 2025-08-08 02:16:05 +08:00
2248356998 qq.com
fa347f4f68 修改报警事件时间字段,增加变量表报警类 2025-08-07 19:19:36 +08:00
Diego
d7df6fc605 10.10.12 2025-08-07 11:24:57 +08:00
2248356998 qq.com
eb4bb2fd48 10.10.11 2025-08-07 10:19:28 +08:00
2248356998 qq.com
faa9858974 10.10.11 2025-08-07 10:18:22 +08:00
2248356998 qq.com
1b3d2dda49 QuestDbRestAPI 2025-08-07 10:11:37 +08:00
Diego
a8a9453611 !70 fix: questdb restapi多实例 2025-08-07 01:58:32 +00:00
2248356998 qq.com
e84f42ce14 10.10.10 2025-08-06 21:42:46 +08:00
2248356998 qq.com
6f814cf6b8 更新questdb restapi启用字段 2025-08-06 21:42:23 +08:00
2248356998 qq.com
e36432e4e9 10.10.9 2025-08-06 19:33:30 +08:00
Diego
ebd71e807b !69 更新依赖 2025-08-06 11:25:31 +00:00
560 changed files with 14522 additions and 9330 deletions

1
.gitignore vendored
View File

@@ -364,6 +364,7 @@ FodyWeavers.xsd
/src/*Pro*/
/src/*Pro*
/src/**/*Pro*
/src/*pro*
/src/*pro*/
/src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json

View File

@@ -11,6 +11,7 @@
<IncludeBuildOutput>false</IncludeBuildOutput>
<!-- 避免 DLL 被打包到 lib/ -->
<EnableSourceGenerator>true</EnableSourceGenerator>
<!-- 可选 -->

View File

@@ -37,9 +37,8 @@ public class FileController : ControllerBase
var root = Directory.GetCurrentDirectory();
var wwwroot = Path.Combine(root, "wwwroot");
var filePath = Path.Combine(wwwroot, fileName);
// 防止路径穿越攻击
#pragma warning disable CA3003
if (!filePath.StartsWith(wwwroot, StringComparison.OrdinalIgnoreCase) || !System.IO.File.Exists(filePath))
if ((!(fileName.StartsWith(@"../Logs") || fileName.StartsWith(@"..\Logs")) && filePath.Contains("..")) || !System.IO.File.Exists(filePath))
{
return NotFound();
}
@@ -49,6 +48,6 @@ public class FileController : ControllerBase
Response.Headers.Append("Access-Control-Expose-Headers", "Content-Disposition");
return File(fileStream, "application/octet-stream", (fileName.Replace('/', '_')));
return File(fileStream, "application/octet-stream", (Path.GetFileName(filePath).Replace('/', '_')));
}
}

View File

@@ -21,16 +21,7 @@
"UserNoModule": "This account has not been assigned a module. Please contact the administrator",
"UserNull": "User {0} does not exist"
},
"ThingsGateway.Admin.Application.BaseDataEntity": {
"CreateOrgId": "CreateOrgId"
},
"ThingsGateway.Admin.Application.BaseEntity": {
"CreateTime": "CreateTime",
"CreateUser": "CreateUser",
"SortCode": "SortCode",
"UpdateTime": "UpdateTime",
"UpdateUser": "UpdateUser"
},
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
"UserExpire": "User expired, please login again"
},

View File

@@ -21,16 +21,7 @@
"UserNoModule": "该账号未分配模块,请联系管理员",
"UserNull": "用户 {0} 不存在"
},
"ThingsGateway.Admin.Application.BaseDataEntity": {
"CreateOrgId": "创建机构Id"
},
"ThingsGateway.Admin.Application.BaseEntity": {
"CreateTime": "创建时间",
"CreateUser": "创建人",
"SortCode": "排序",
"UpdateTime": "更新时间",
"UpdateUser": "更新人"
},
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
"UserExpire": "用户登录已过期,请重新登录"
},

View File

@@ -377,9 +377,9 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
/// 获取用户拥有的资源
/// </summary>
/// <param name="id">用户id</param>
public async Task<GrantResourceData> OwnResourceAsync(long id)
public Task<GrantResourceData> OwnResourceAsync(long id)
{
return await _roleService.OwnResourceAsync(id, RelationCategoryEnum.UserHasResource).ConfigureAwait(false);
return _roleService.OwnResourceAsync(id, RelationCategoryEnum.UserHasResource);
}
/// <summary>
@@ -505,10 +505,10 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
var password = await GetDefaultPassWord(true).ConfigureAwait(false);//获取默认密码,这里不走Aop所以需要加密一下
using var db = GetDB();
//重置密码
if (await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
if ((await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
{
Password = password
}, it => it.Id == id).ConfigureAwait(false))
}, it => it.Id == id).ConfigureAwait(false)) > 0)
{
DeleteUserFromCache(id);//从cache删除用户信息
var verificatInfoIds = _verificatInfoService.GetListByUserId(id);

View File

@@ -185,12 +185,12 @@ internal sealed class UserCenterService : BaseService<SysUser>, IUserCenterServi
using var db = GetDB();
//更新指定字段
var result = await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
var result = (await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
{
Email = input.Email,
Phone = input.Phone,
Avatar = input.Avatar,
}, it => it.Id == UserManager.UserId).ConfigureAwait(false);
}, it => it.Id == UserManager.UserId).ConfigureAwait(false)) > 0;
if (result)
_userService.DeleteUserFromCache(UserManager.UserId);//cache删除用户数据
}

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>

View File

@@ -30,7 +30,7 @@ public class BlazorAppContext
/// <summary>
/// 全部菜单
/// </summary>
public IEnumerable<SysResource> AllMenus { get; private set; }
public List<SysResource> AllMenus { get; private set; }
/// <summary>
/// 当前用户
@@ -42,22 +42,22 @@ public class BlazorAppContext
/// <summary>
/// 用户个人菜单
/// </summary>
public IEnumerable<MenuItem> OwnMenuItems { get; private set; }
public List<MenuItem> OwnMenuItems { get; private set; }
/// <summary>
/// 不同模块的菜单
/// </summary>
public IEnumerable<MenuItem> AllOwnMenuItems { get; private set; }
public List<MenuItem> AllOwnMenuItems { get; private set; }
/// <summary>
/// 用户个人菜单,多个模块
/// </summary>
public IEnumerable<SysResource> OwnMenus { get; private set; }
public List<SysResource> OwnMenus { get; private set; }
/// <summary>
/// 用户个人菜单,非树形
/// </summary>
public IEnumerable<MenuItem> OwnSameLevelMenuItems { get; private set; }
public List<MenuItem> OwnSameLevelMenuItems { get; private set; }
/// <summary>
/// 个人工作台
@@ -67,9 +67,9 @@ public class BlazorAppContext
/// <summary>
/// 用户个人快捷方式菜单
/// </summary>
public IEnumerable<SysResource> UserWorkbenchOutputs { get; private set; }
public List<SysResource> UserWorkbenchOutputs { get; private set; }
public IEnumerable<SysResource> AllResource { get; private set; }
public List<SysResource> AllResource { get; private set; }
private ISysResourceService ResourceService { get; }
private ISysUserService SysUserService { get; }
@@ -93,7 +93,7 @@ public class BlazorAppContext
AllResource = sysResources;
var ids = CurrentUser.ModuleList.Select(a => a.Id).ToHashSet();
CurrentUser.ModuleList = AllResource.Where(a => ids.Contains(a.Id)).OrderBy(a => a.SortCode).ToList();
AllMenus = AllResource.Where(a => a.Category == ResourceCategoryEnum.Menu);
AllMenus = AllResource.Where(a => a.Category == ResourceCategoryEnum.Menu).ToList();
if (moduleId == null)
{
@@ -123,8 +123,8 @@ public class BlazorAppContext
}
}
var ownMenus = OwnMenus.Where(a => a.Module == CurrentModuleId);
OwnMenuItems = ResourceUtil.BuildMenuTrees(ownMenus).ToList();
AllOwnMenuItems = ResourceUtil.BuildMenuTrees(OwnMenus).ToList();
OwnMenuItems = AdminResourceUtil.BuildMenuTrees(ownMenus).ToList();
AllOwnMenuItems = AdminResourceUtil.BuildMenuTrees(OwnMenus).ToList();
OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item => new MenuItem()
{
Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All,
@@ -132,8 +132,8 @@ public class BlazorAppContext
Icon = item.Icon,
Url = item.Href,
Target = item.Target.ToString(),
});
UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id));
}).ToList();
UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)).ToList();
}
}

View File

@@ -29,7 +29,7 @@ public partial class EditPagePolicy
protected override Task OnParametersSetAsync()
{
ShortcutsTreeViewItems = ResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null);
ShortcutsTreeViewItems = AdminResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null);
return base.OnParametersSetAsync();
}
@@ -48,6 +48,6 @@ public partial class EditPagePolicy
{
await Task.CompletedTask;
ShortcutsSearchText = searchText;
return ResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null);
return AdminResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null);
}
}

View File

@@ -41,7 +41,7 @@ public partial class MenuChoiceDialog
var all = (await SysResourceService.GetAllAsync());
var items = all.Where(a => a.Category == ResourceCategoryEnum.Menu && a.Module == ModuleId);
ModuleTitle = all.FirstOrDefault(a => a.Id == ModuleId)?.Title;
Items = ResourceUtil.BuildTreeItemList(items, new List<long> { Value }, RenderTreeItem);
Items = AdminResourceUtil.BuildTreeItemList(items, new List<long> { Value }, RenderTreeItem);
await base.OnParametersSetAsync();
}

View File

@@ -26,12 +26,12 @@
OnQueryAsync="OnQueryAsync" CustomerSearchModel="@CustomerSearchModel"
OnSaveAsync="Save" OnDeleteAsync="Delete">
<TableToolbarTemplate>
<PopConfirmButton Color=Color.Warning IsDisabled="SelectedRows.Count<=0||!AuthorizeButton(AdminOperConst.Add)" Text=@OperDescLocalizer["CopyResource"] Icon="fa fa-copy" OnConfirm="OnCopy">
<PopConfirmButton Color=Color.Warning IsKeepDisabled="SelectedRows.Count <= 0 || !AuthorizeButton(AdminOperConst.Add)" Text=@OperDescLocalizer["CopyResource"] Icon="fa fa-copy" OnConfirm="OnCopy">
<BodyTemplate>
<Select Items="ModuleSelectedItems" @bind-Value=CopyModule ShowLabel="false" />
</BodyTemplate>
</PopConfirmButton>
<PopConfirmButton Color=Color.Warning IsDisabled="SelectedRows.Count!=1||!AuthorizeButton(AdminOperConst.Edit)" Text=@OperDescLocalizer["ChangeParentResource"] Icon="fa fa-copy" OnConfirm="OnChangeParent">
<PopConfirmButton Color=Color.Warning IsKeepDisabled="SelectedRows.Count != 1 || !AuthorizeButton(AdminOperConst.Edit)" Text=@OperDescLocalizer["ChangeParentResource"] Icon="fa fa-copy" OnConfirm="OnChangeParent">
<BodyTemplate>
<div class="overflow-y-auto" style="height:500px">
<TreeView Items="MenuTreeItems" IsVirtualize="true" OnTreeItemClick="a=>{ChangeParentId=a.Value.Id;return Task.CompletedTask;}" />

View File

@@ -39,8 +39,8 @@ public partial class SysResourcePage
protected override async Task OnParametersSetAsync()
{
ModuleSelectedItems = ResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
MenuItems = ResourceUtil.BuildMenuSelectList((await SysResourceService.GetAllAsync())).Concat(new List<SelectedItem>() { new("0", AdminLocalizer["Root"]) }).ToList();
ModuleSelectedItems = AdminResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
MenuItems = AdminResourceUtil.BuildMenuSelectList((await SysResourceService.GetAllAsync())).Concat(new List<SelectedItem>() { new("0", AdminLocalizer["Root"]) }).ToList();
await base.OnParametersSetAsync();
}
@@ -49,7 +49,7 @@ public partial class SysResourcePage
private async Task<QueryData<SysResource>> OnQueryAsync(QueryPageOptions options)
{
MenuTreeItems = new List<TreeViewItem<SysResource>>() { new TreeViewItem<SysResource>(new SysResource()) { Text = AdminLocalizer["Root"] } }.Concat(ResourceUtil.BuildTreeItemList((await SysResourceService.GetAllAsync()).Where(a => a.Module == CustomerSearchModel.Module), new(), null)).ToList();
MenuTreeItems = new List<TreeViewItem<SysResource>>() { new TreeViewItem<SysResource>(new SysResource()) { Text = AdminLocalizer["Root"] } }.Concat(AdminResourceUtil.BuildTreeItemList((await SysResourceService.GetAllAsync()).Where(a => a.Module == CustomerSearchModel.Module), new(), null)).ToList();
var data = await SysResourceService.PageAsync(options, CustomerSearchModel);
return data;
@@ -136,14 +136,14 @@ public partial class SysResourcePage
private async Task<IEnumerable<TableTreeNode<SysResource>>> OnTreeExpand(SysResource menu)
{
var sysResources = await SysResourceService.GetAllAsync();
var result = ResourceUtil.BuildTableTrees(sysResources, menu.Id);
var result = AdminResourceUtil.BuildTableTrees(sysResources, menu.Id);
return result;
}
private static async Task<IEnumerable<TableTreeNode<SysResource>>> TreeNodeConverter(IEnumerable<SysResource> items)
{
await Task.CompletedTask;
var result = ResourceUtil.BuildTableTrees(items, 0);
var result = AdminResourceUtil.BuildTableTrees(items, 0);
return result;
}

View File

@@ -35,7 +35,7 @@ public partial class GrantResourceDialog
{
var items = (await SysResourceService.GetAllAsync()).Where(a => a.Category != ResourceCategoryEnum.Module).OrderBy(a => a.Module).ThenBy(a => a.Id).ToList();
Items = ResourceUtil.BuildTreeItemList(items, Value, RenderTreeItem);
Items = AdminResourceUtil.BuildTreeItemList(items, Value, RenderTreeItem);
ModuleList = (await SysResourceService.GetAllAsync()).Where(a => a.Category == ResourceCategoryEnum.Module).ToList();
await base.OnInitializedAsync();
}

View File

@@ -35,7 +35,7 @@ public partial class SysUserEdit
BoolItems = LocalizerUtil.GetBoolItems(Model.GetType(), nameof(Model.Status));
var items = await SysPositionService.SelectorAsync(new PositionSelectorInput());
Items = PositionUtil.BuildCascaderItemList(items);
ModuleSelectedItems = ResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
ModuleSelectedItems = AdminResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
await InvokeAsync(StateHasChanged);
await base.OnInitializedAsync();
}

View File

@@ -18,6 +18,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>-->
</PropertyGroup>
<ItemGroup>

View File

@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Razor;
/// <inheritdoc/>
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class ResourceUtil
public static class AdminResourceUtil
{
/// <summary>
/// 构造选择项ID/TITLE

View File

@@ -29,7 +29,7 @@
<Target Name="AdminPostPublish" AfterTargets="Publish">
<ItemGroup>
<!-- setting up the variable for convenience -->
<AdminFiles Include="bin\$(Configuration)\$(TargetFramework)\SeedData\**" />
<AdminFiles Include="$(OutputPath)\$(TargetFramework)\SeedData\**" />
</ItemGroup>
<PropertyGroup>
</PropertyGroup>

View File

@@ -5,7 +5,8 @@
#推送docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway
#aspnetcore9.0环境
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
#FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble AS base
COPY . /app
WORKDIR /app
#默认web
@@ -13,6 +14,8 @@ EXPOSE 5000
# 添加时区环境变量,亚洲,上海
ENV TimeZone=Asia/Shanghai
# 转发头
ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
# 使用软连接,并且将时区配置覆盖/etc/timezone
RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone

View File

@@ -5,7 +5,8 @@
#推送docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64
#aspnetcore9.0环境
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine-arm64v8 AS base
#FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine-arm64v8 AS base
FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble-arm64v8 AS base
COPY . /app
WORKDIR /app
#默认web
@@ -13,6 +14,8 @@ EXPOSE 5000
# 添加时区环境变量,亚洲,上海
ENV TimeZone=Asia/Shanghai
# 转发头
ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
# 使用软连接,并且将时区配置覆盖/etc/timezone
RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone

View File

@@ -39,7 +39,7 @@
<BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" />
<script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src="_framework/blazor.web.js"></script>
<!-- PWA Service Worker -->
<script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>

View File

@@ -45,7 +45,7 @@
</app>
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js")></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script>
<script src="_framework/blazor.server.js"></script>
<!-- PWA Service Worker -->

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
</PropertyGroup>
<!--<Import Project="Admin.targets" Condition=" '$(Configuration)' != 'Debug' " />-->

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
@@ -13,7 +14,7 @@
<ItemGroup>
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
<PackageReference Include="BootstrapBlazor" Version="9.9.1" />
<PackageReference Include="BootstrapBlazor" Version="9.9.3" />
</ItemGroup>
<ItemGroup>

View File

@@ -53,6 +53,8 @@ public static class QueryPageOptionsExtensions
return datas;
}
public static IEnumerable<T> GetQuery<T>(this IEnumerable<T> query, QueryPageOptions option, Func<IEnumerable<T>, IEnumerable<T>>? queryFunc = null, FilterKeyValueAction where = null)
{
if (queryFunc != null)
@@ -123,7 +125,36 @@ public static class QueryPageOptionsExtensions
};
var items = datas.GetData(option, out var totalCount, where);
ret.TotalCount = totalCount;
if (totalCount > 0)
{
if (!items.Any() && option.PageIndex != 1)
{
option.PageIndex = 1;
items = datas.GetData(option, out totalCount, where);
}
}
ret.Items = items.ToList();
return ret;
}
/// <summary>
/// 根据查询条件返回QueryData
/// </summary>
public static QueryData<SelectedItem> GetQueryData<T>(this IEnumerable<T> datas, VirtualizeQueryOption option, Func<IEnumerable<T>, IEnumerable<SelectedItem>> func, FilterKeyValueAction where = null)
{
var ret = new QueryData<SelectedItem>()
{
IsSorted = false,
IsFiltered = false,
IsAdvanceSearch = false,
IsSearch = !option.SearchText.IsNullOrWhiteSpace()
};
var items = datas.Skip((option.StartIndex)).Take(option.Count);
ret.TotalCount = datas.Count();
ret.Items = func(items).ToList();
return ret;
}
}

View File

@@ -209,16 +209,10 @@ public static class SqlSugarExtensions
}
/// <inheritdoc/>
public static async Task<bool> UpdateRangeAsync<T>(this SqlSugarClient db, List<T> updateObjs) where T : class, new()
public static Task<int> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new()
{
return await db.Updateable(updateObjs).ExecuteCommandAsync().ConfigureAwait(false) > 0;
}
/// <inheritdoc/>
public static async Task<bool> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new()
{
return await db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression)
.ExecuteCommandAsync().ConfigureAwait(false) > 0;
return db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression)
.ExecuteCommandAsync();
}
private static IEnumerable<T> Sort<T>(this IEnumerable<T> list, BasePageInput basePageInput)

View File

@@ -0,0 +1,13 @@
{
"ThingsGateway.Admin.Application.BaseDataEntity": {
"CreateOrgId": "CreateOrgId"
},
"ThingsGateway.Admin.Application.BaseEntity": {
"CreateTime": "CreateTime",
"CreateUser": "CreateUser",
"SortCode": "SortCode",
"UpdateTime": "UpdateTime",
"UpdateUser": "UpdateUser"
}
}

View File

@@ -0,0 +1,13 @@
{
"ThingsGateway.DB.BaseDataEntity": {
"CreateOrgId": "创建机构Id"
},
"ThingsGateway.DB.BaseEntity": {
"CreateTime": "创建时间",
"CreateUser": "创建人",
"SortCode": "排序",
"UpdateTime": "更新时间",
"UpdateUser": "更新人"
}
}

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
@@ -18,6 +19,12 @@
<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Locales\en-US.json" />
<EmbeddedResource Include="Locales\zh-CN.json" />
</ItemGroup>
<ItemGroup>
<!--<PackageReference Include="ThingsGateway.Razor" Version="$(SourceGeneratorVersion)" />-->
<!--<ProjectReference Include="..\ThingsGateway.Razor\ThingsGateway.Razor.csproj" />-->

View File

@@ -554,10 +554,9 @@ public static class App
{
types = ass.GetTypes();
}
catch
catch (Exception ex)
{
XTrace.Log.Warn($"Error load `{ass.FullName}` assembly.");
Console.WriteLine($"Error load `{ass.FullName}` assembly.");
XTrace.Log.Warn($"Error load `{ass.FullName}` assembly. : {ex.Message}");
}
return types.Where(u => u.IsPublic && !u.IsDefined(typeof(SuppressSnifferAttribute), false));

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
</PropertyGroup>
<PropertyGroup>

View File

@@ -0,0 +1,45 @@
namespace ThingsGateway.NewLife;
using System;
using System.Collections;
using System.Collections.Generic;
public class BoundedQueue<T> : IEnumerable<T>
{
private readonly Queue<T> _queue;
private readonly int _capacity;
private readonly object _syncRoot = new object();
public BoundedQueue(int capacity)
{
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
_capacity = capacity;
_queue = new Queue<T>(capacity);
}
public void Enqueue(T item)
{
lock (_syncRoot)
{
if (_queue.Count == _capacity)
_queue.Dequeue();
_queue.Enqueue(item);
}
}
public int Count
{
get { lock (_syncRoot) return _queue.Count; }
}
public IEnumerator<T> GetEnumerator()
{
lock (_syncRoot)
{
return new List<T>(_queue).GetEnumerator();
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@@ -21,6 +21,7 @@ namespace ThingsGateway.NewLife.Json.Extension;
/// </summary>
public static class SystemTextJsonExtension
{
/// <summary>
/// 默认Json规则带缩进
/// </summary>
@@ -31,37 +32,51 @@ public static class SystemTextJsonExtension
/// </summary>
public static JsonSerializerOptions NoneIndentedOptions;
/// <summary>
/// 默认Json规则带缩进
/// </summary>
public static JsonSerializerOptions IgnoreNullIndentedOptions;
/// <summary>
/// 默认Json规则无缩进
/// </summary>
public static JsonSerializerOptions IgnoreNullNoneIndentedOptions;
public static JsonSerializerOptions GetOptions(bool writeIndented, bool ignoreNull)
{
var options = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = writeIndented,
DefaultIgnoreCondition = ignoreNull
? JsonIgnoreCondition.WhenWritingNull
: JsonIgnoreCondition.Never,
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
};
options.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
options.Converters.Add(new JTokenSystemTextJsonConverter());
options.Converters.Add(new JValueSystemTextJsonConverter());
options.Converters.Add(new JObjectSystemTextJsonConverter());
options.Converters.Add(new JArraySystemTextJsonConverter());
return options;
}
static SystemTextJsonExtension()
{
IndentedOptions = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true, // 缩进
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // 忽略 null
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
};
// 如有自定义Converter这里添加
// IndentedOptions.Converters.Add(new ByteArrayJsonConverter());
IndentedOptions.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
IndentedOptions.Converters.Add(new JTokenSystemTextJsonConverter());
IndentedOptions.Converters.Add(new JValueSystemTextJsonConverter());
IndentedOptions.Converters.Add(new JObjectSystemTextJsonConverter());
IndentedOptions.Converters.Add(new JArraySystemTextJsonConverter());
NoneIndentedOptions = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false, // 不缩进
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
};
NoneIndentedOptions.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
NoneIndentedOptions.Converters.Add(new JTokenSystemTextJsonConverter());
NoneIndentedOptions.Converters.Add(new JValueSystemTextJsonConverter());
NoneIndentedOptions.Converters.Add(new JObjectSystemTextJsonConverter());
NoneIndentedOptions.Converters.Add(new JArraySystemTextJsonConverter());
// NoneIndentedOptions.Converters.Add(new ByteArrayJsonConverter());
IndentedOptions = GetOptions(true, false);
NoneIndentedOptions = GetOptions(false, false);
IgnoreNullIndentedOptions = GetOptions(true, true);
IgnoreNullNoneIndentedOptions = GetOptions(false, true);
}
/// <summary>
/// 反序列化
/// </summary>
@@ -96,17 +111,17 @@ public static class SystemTextJsonExtension
/// <summary>
/// 序列化
/// </summary>
public static string ToSystemTextJsonString(this object item, bool indented = true)
public static string ToSystemTextJsonString(this object item, bool indented = true, bool ignoreNull = true)
{
return JsonSerializer.Serialize(item, item?.GetType() ?? typeof(object), indented ? IndentedOptions : NoneIndentedOptions);
return JsonSerializer.Serialize(item, item?.GetType() ?? typeof(object), ignoreNull ? indented ? IgnoreNullIndentedOptions : IgnoreNullNoneIndentedOptions : indented ? IndentedOptions : NoneIndentedOptions);
}
/// <summary>
/// 序列化
/// </summary>
public static byte[] ToSystemTextJsonUtf8Bytes(this object item, bool indented = true)
public static byte[] ToSystemTextJsonUtf8Bytes(this object item, bool indented = true, bool ignoreNull = true)
{
return JsonSerializer.SerializeToUtf8Bytes(item, item.GetType(), indented ? IndentedOptions : NoneIndentedOptions);
return JsonSerializer.SerializeToUtf8Bytes(item, item.GetType(), ignoreNull ? indented ? IgnoreNullIndentedOptions : IgnoreNullNoneIndentedOptions : indented ? IndentedOptions : NoneIndentedOptions);
}
}

View File

@@ -190,7 +190,31 @@ public sealed class Crc32 //: HashAlgorithm
crc.Update(stream, count);
return crc.Value;
}
/// <summary>
/// 添加Sequence进行校验
/// </summary>
/// <param name="sequence"></param>
/// <returns></returns>
public Crc32 Update(ReadOnlySequence<byte> sequence)
{
foreach (var segment in sequence)
{
Update(segment.Span);
}
return this;
}
/// <summary>
/// 计算校验码 (Sequence)
/// </summary>
/// <param name="sequence"></param>
/// <returns></returns>
public static UInt32 Compute(ReadOnlySequence<byte> sequence)
{
var crc = new Crc32();
crc.Update(sequence);
return crc.Value;
}
//#region 抽象实现
///// <summary>哈希核心</summary>
///// <param name="array"></param>

View File

@@ -12,6 +12,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>newlife.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

View File

@@ -306,8 +306,19 @@ public class TimerScheduler : ILogFeature
return;
}
var func = timer.Method.As<Func<Object?, Task>>(target);
await func!(timer.State).ConfigureAwait(false);
#if NET6_0_OR_GREATER
if (timer.IsValueTask)
{
var func = timer.Method.As<Func<Object?, ValueTask>>(target);
await func!(timer.State).ConfigureAwait(false);
}
else
#endif
{
var func = timer.Method.As<Func<Object?, Task>>(target);
await func!(timer.State).ConfigureAwait(false);
}
}
catch (ThreadAbortException) { throw; }
catch (ThreadInterruptedException) { throw; }

View File

@@ -87,6 +87,8 @@ public class TimerX : ITimer, ITimerx, IDisposable
private DateTime _AbsolutelyNext;
private readonly Cron[]? _crons;
internal bool IsValueTask { get; }
#endregion
// #region 静态
@@ -158,6 +160,29 @@ public class TimerX : ITimer, ITimerx, IDisposable
Init(dueTime);
}
#if NET6_0_OR_GREATER
/// <summary>实例化一个不可重入的定时器</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
/// <param name="dueTime">多久之后开始。毫秒</param>
/// <param name="period">间隔周期。毫秒</param>
/// <param name="scheduler">调度器</param>
public TimerX(Func<Object, ValueTask> callback, Object? state, Int32 dueTime, Int32 period, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler)
{
IsValueTask = true;
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (dueTime < 0) throw new ArgumentOutOfRangeException(nameof(dueTime));
IsAsyncTask = true;
Async = true;
Period = period;
Init(dueTime);
}
#endif
/// <summary>实例化一个绝对定时器指定时刻执行跟当前时间和SetNext无关</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
@@ -210,6 +235,37 @@ public class TimerX : ITimer, ITimerx, IDisposable
Init(ms);
}
#if NET6_0_OR_GREATER
/// <summary>实例化一个绝对定时器指定时刻执行跟当前时间和SetNext无关</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
/// <param name="startTime">绝对开始时间</param>
/// <param name="period">间隔周期。毫秒</param>
/// <param name="scheduler">调度器</param>
public TimerX(Func<Object, ValueTask> callback, Object? state, DateTime startTime, Int32 period, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler)
{
IsValueTask = true;
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (startTime <= DateTime.MinValue) throw new ArgumentOutOfRangeException(nameof(startTime));
if (period <= 0) throw new ArgumentOutOfRangeException(nameof(period));
IsAsyncTask = true;
Async = true;
Period = period;
Absolutely = true;
//var now = DateTime.Now;
var now = Scheduler.GetNow();
var next = startTime;
while (next < now) next = next.AddMilliseconds(period);
var ms = (Int64)(next - now).TotalMilliseconds;
_AbsolutelyNext = next;
Init(ms);
}
#endif
/// <summary>实例化一个Cron定时器</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
@@ -274,6 +330,42 @@ public class TimerX : ITimer, ITimerx, IDisposable
//Init(_AbsolutelyNext = _cron.GetNext(DateTime.Now));
}
#if NET6_0_OR_GREATER
/// <summary>实例化一个Cron定时器</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
/// <param name="cronExpression">Cron表达式。支持多个表达式分号分隔</param>
/// <param name="scheduler">调度器</param>
public TimerX(Func<Object, ValueTask> callback, Object? state, String cronExpression, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler)
{
IsValueTask = true;
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (cronExpression.IsNullOrEmpty()) throw new ArgumentNullException(nameof(cronExpression));
var list = new List<Cron>();
foreach (var item in cronExpression.Split(";"))
{
var cron = new Cron();
if (!cron.Parse(item)) throw new ArgumentException($"Invalid Cron expression[{item}]", nameof(cronExpression));
list.Add(cron);
}
_crons = list.ToArray();
IsAsyncTask = true;
Async = true;
Absolutely = true;
//var now = DateTime.Now;
var now = Scheduler.GetNow();
var next = _crons.Min(e => e.GetNext(now));
var ms = (Int64)(next - now).TotalMilliseconds;
_AbsolutelyNext = next;
Init(ms);
//Init(_AbsolutelyNext = _cron.GetNext(DateTime.Now));
}
#endif
public bool Disposed { get; private set; }
/// <summary>销毁定时器</summary>
public void Dispose()

View File

@@ -4,6 +4,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@@ -8,8 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Common.Extension;
using ThingsGateway.NewLife;
using ThingsGateway.Razor.Extension;
namespace ThingsGateway.Razor;

View File

@@ -5,7 +5,7 @@
<Step @ref="@step" IsVertical="true">
<StepItem Text=@Localizer["First"] Title=@Localizer["Upload"]>
<InputUpload ShowDeleteButton="true" @bind-Value=_importFile Accept=".xlsx"></InputUpload>
<Button class="mt-2" IsAsync OnClick="() => DeviceImport(_importFile)">@Localizer["Validate"]</Button>
<PopConfirmButton IsAsync Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
</StepItem>
<StepItem Text=@Localizer["Second"] Title=@Localizer["ValidateText"]>
@@ -41,16 +41,12 @@
}
<PopConfirmButton IsAsync IsDisabled=@_importPreviews.Any(it => it.Value.HasError) Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
<Button class="mt-2" IsAsync OnClick="() => DeviceImport()">@RazorLocalizer["Close"]</Button>
@*
<Button IsAsync class="mt-2" IsDisabled=@_importPreviews.Any(it => it.Value.HasError) OnClick="() => step.Next()">@Localizer["Next"]</Button> *@
</div>
</StepItem>
@* <StepItem Text=@Localizer["Third"] Title=@Localizer["Import"]>
<PopConfirmButton IsAsync Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
</StepItem> *@
</Step>
@code {
[NotNull]

View File

@@ -24,18 +24,17 @@ public partial class ImportExcel
/// </summary>
[Parameter]
[EditorRequired]
public Func<Dictionary<string, ImportPreviewOutputBase>, Task> Import { get; set; }
public Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> Import { get; set; }
[Inject]
[NotNull]
private IStringLocalizer<ImportExcel>? Localizer { get; set; }
/// <summary>
/// 预览
/// </summary>
[Parameter]
[EditorRequired]
public Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> Preview { get; set; }
[Inject]
[NotNull]
private IStringLocalizer<ThingsGateway.Razor._Imports>? RazorLocalizer { get; set; }
[Inject]
[NotNull]
@@ -47,13 +46,17 @@ public partial class ImportExcel
[CascadingParameter]
private Func<Task>? OnCloseAsync { get; set; }
private async Task DeviceImport(IBrowserFile file)
private async Task DeviceImport()
{
try
{
_importPreviews.Clear();
_importPreviews = await Preview.Invoke(file);
await step.Next();
await InvokeAsync(async () =>
{
if (OnCloseAsync != null)
await OnCloseAsync();
await ToastService.Default();
});
}
catch (Exception ex)
{
@@ -67,16 +70,12 @@ public partial class ImportExcel
{
await Task.Run(async () =>
{
await Import.Invoke(_importPreviews);
_importPreviews = await Import.Invoke(_importFile);
_importFile = null;
await InvokeAsync(async () =>
{
if (OnCloseAsync != null)
await OnCloseAsync();
await ToastService.Default();
});
});
await step.Next();
}
catch (Exception ex)
{

View File

@@ -0,0 +1,58 @@
@using ThingsGateway.Extension
@namespace ThingsGateway.Razor
<Button OnClick="() => step.Reset()">@Localizer["Reset"]</Button>
<h6 class="my-3 green--text">@Localizer["Tip"] </h6>
<Step @ref="@step" IsVertical="true">
<StepItem Text=@Localizer["First"] Title=@Localizer["Upload"]>
<InputUpload ShowDeleteButton="true" @bind-Value=_importFile Accept=".xlsx"></InputUpload>
<Button class="mt-2" IsAsync OnClick="() => DeviceImport(_importFile)">@Localizer["Validate"]</Button>
</StepItem>
<StepItem Text=@Localizer["Second"] Title=@Localizer["ValidateText"]>
<div class="overflow-y-auto">
@foreach (var item in _importPreviews)
{
<div class="mt-2">
@(
Localizer["UploadCount", item.Key, item.Value.DataCount]
)
</div>
<div class=@((item.Value.HasError ? "my-2 red--text" : "my-2 green--text"))>
@(
(item.Value.HasError ? "Error" : "Success")
)
</div>
if (item.Value.HasError)
{
<div style="height:300px;" class="overflow-y-scroll">
<Virtualize Items="item.Value.Results.Where(a => !a.Success).OrderBy(a => a.Row).ToList()" Context="item1" ItemSize="60" OverscanCount=2>
<ItemContent>
<div class="row g-0">
<span class="col mx-2">@item1.Row</span>
<span class=@((item1.Success ? "green--text col-auto" : "red--text col-auto"))>
<strong>@item1.ErrorMessage</strong>
</span>
</div>
</ItemContent>
</Virtualize>
</div>
}
}
<PopConfirmButton IsAsync IsKeepDisabled=@_importPreviews.Any(it => it.Value.HasError) Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
@*
<Button IsAsync class="mt-2" IsDisabled=@_importPreviews.Any(it => it.Value.HasError) OnClick="() => step.Next()">@Localizer["Next"]</Button> *@
</div>
</StepItem>
@* <StepItem Text=@Localizer["Third"] Title=@Localizer["Import"]>
<PopConfirmButton IsAsync Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
</StepItem> *@
</Step>
@code {
[NotNull]
Step? step { get; set; }
}

View File

@@ -0,0 +1,86 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Components.Forms;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Razor;
/// <inheritdoc/>
public partial class ImportExcelConfirm
{
private Dictionary<string, ImportPreviewOutputBase> _importPreviews = new();
/// <summary>
/// 导入
/// </summary>
[Parameter]
[EditorRequired]
public Func<Dictionary<string, ImportPreviewOutputBase>, Task> Import { get; set; }
[Inject]
[NotNull]
private IStringLocalizer<ImportExcelConfirm>? Localizer { get; set; }
/// <summary>
/// 预览
/// </summary>
[Parameter]
[EditorRequired]
public Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> Preview { get; set; }
[Inject]
[NotNull]
private ToastService? ToastService { get; set; }
[Required]
private IBrowserFile _importFile { get; set; }
[CascadingParameter]
private Func<Task>? OnCloseAsync { get; set; }
private async Task DeviceImport(IBrowserFile file)
{
try
{
_importPreviews.Clear();
_importPreviews = await Preview.Invoke(file);
await step.Next();
}
catch (Exception ex)
{
await ToastService.Warn(ex);
}
}
private async Task SaveDeviceImport()
{
try
{
await Task.Run(async () =>
{
await Import.Invoke(_importPreviews);
_importFile = null;
await InvokeAsync(async () =>
{
if (OnCloseAsync != null)
await OnCloseAsync();
await ToastService.Default();
});
});
}
catch (Exception ex)
{
await InvokeAsync(async () => await ToastService.Warn(ex));
}
}
}

View File

@@ -0,0 +1,9 @@
::deep .avatar {
border-radius: 1.5rem;
width: 24px;
height: 24px;
background-color: var(--bs-red);
color: #fff;
flex: 0 0 auto;
font-size: 1rem;
}

View File

@@ -10,7 +10,7 @@
using Microsoft.JSInterop;
namespace ThingsGateway.Common.Extension;
namespace ThingsGateway.Razor.Extension;
/// <summary>
/// JSRuntime扩展方法
@@ -49,4 +49,28 @@ public static class JSRuntimeExtensions
{
}
}
public static async ValueTask<T> GetLocalStorage<T>(this IJSRuntime jsRuntime, string name)
{
try
{
return await jsRuntime.InvokeAsync<T>("getLocalStorage", name).ConfigureAwait(false);
}
catch
{
return default;
}
}
public static async ValueTask SetLocalStorage<T>(this IJSRuntime jsRuntime, string name, T data)
{
try
{
await jsRuntime.InvokeVoidAsync("setLocalStorage", name, data).ConfigureAwait(false);
}
catch
{
}
}
}

View File

@@ -25,7 +25,8 @@
"Success": "Success",
"TablesExportButtonExcelText": "Export Excel",
"TablesImportButtonExcelText": "Import Excel",
"True": "Yes"
"True": "Yes",
"Info": "Info"
},
"ThingsGateway.Razor.About": {
"Community": "Community",
@@ -59,6 +60,19 @@
"SearchText": "Search Page"
},
"ThingsGateway.Razor.ImportExcel": {
"First": "Step 1",
"Import": "If there are no errors during verification, it will be directly imported into the database",
"Next": "Next",
"Reset": "Reset",
"Second": "Step 2",
"Third": "Step 3",
"Tip": "When the data volume is large (more than 200,000), the import may take more than 1 minute, please be patient",
"Upload": "Upload File",
"UploadCount": " Table {0}, import {1} records",
"Validate": "Validate",
"ValidateText": "Validation Content"
},
"ThingsGateway.Razor.ImportExcelConfirm": {
"First": "Step 1",
"Import": "Import",
"Next": "Next",
@@ -67,7 +81,7 @@
"Third": "Step 3",
"Tip": "When the data volume is large (more than 200,000), the import may take more than 1 minute, please be patient",
"Upload": "Upload File",
"UploadCount": " Table {0}, expecting to import {1} records",
"UploadCount": " Table {0}, import {1} records",
"Validate": "Validate",
"ValidateText": "Validation Content"
},

View File

@@ -25,7 +25,8 @@
"Success": "成功",
"TablesExportButtonExcelText": "导出Excel",
"TablesImportButtonExcelText": "导入Excel",
"True": "是"
"True": "是",
"Info": "详情"
},
"ThingsGateway.Razor.About": {
"Community": "社区",
@@ -59,6 +60,19 @@
"SearchText": "搜索页面"
},
"ThingsGateway.Razor.ImportExcel": {
"First": "第一步",
"Import": "若验证无错误,将直接导入数据库",
"Next": "下一步",
"Reset": "重置",
"Second": "第二步",
"Third": "第三",
"Tip": "数据量较大时(大于20万)所需导入时间可能超过1分钟请耐心等待",
"Upload": "上传文件",
"UploadCount": " 表 {0},导入 {1} 条数据",
"Validate": "验证",
"ValidateText": "验证内容"
},
"ThingsGateway.Razor.ImportExcelConfirm": {
"First": "第一步",
"Import": "导入",
"Next": "下一步",
@@ -67,7 +81,7 @@
"Third": "第三",
"Tip": "数据量较大时(大于20万)所需导入时间可能超过1分钟请耐心等待",
"Upload": "上传文件",
"UploadCount": " 表 {0}预计导入 {1} 条数据",
"UploadCount": " 表 {0},导入 {1} 条数据",
"Validate": "验证",
"ValidateText": "验证内容"
},

View File

@@ -3,6 +3,7 @@
<Import Project="..\..\PackNuget.props" />
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.1.0" />

View File

@@ -1,9 +0,0 @@
// 设置 culture
function setCultureLocalStorage(culture) {
localStorage.setItem("culture", culture);
}
// 获取 culture
function getCultureLocalStorage() {
return localStorage.getItem("culture");
}

View File

@@ -0,0 +1,18 @@
// 设置 culture
function setCultureLocalStorage(culture) {
localStorage.setItem("culture", culture);
}
// 获取 culture
function getCultureLocalStorage() {
return localStorage.getItem("culture");
}
function getLocalStorage(name) {
return JSON.parse(localStorage.getItem(name)) ?? 0;
}
function setLocalStorage(name, data) {
if (localStorage) {
localStorage.setItem(name, JSON.stringify(data));
}
}

View File

@@ -27,16 +27,17 @@
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertDatas">要插入的数据列表</param>
/// <param name="tableName">表名称</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>插入的记录数</returns>
public int BulkCopy<T>(IEnumerable<T> insertDatas, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
public int BulkCopy<T>(IEnumerable<T> insertDatas, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
int result = 0;
// 使用分页方式处理大数据量插入
db.Utilities.PageEach(insertDatas, pageSize, pageItems =>
{
// 同步调用批量插入API并累加结果
result += questDbRestAPI.BulkCopyAsync(pageItems, dateFormat).GetAwaiter().GetResult();
result += questDbRestAPI.BulkCopyAsync(pageItems, tableName, dateFormat).GetAwaiter().GetResult();
});
return result;
}
@@ -46,16 +47,17 @@
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertDatas">要插入的数据列表</param>
/// <param name="tableName">表名称</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>插入的记录数</returns>
public async Task<int> BulkCopyAsync<T>(IEnumerable<T> insertDatas, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
public async Task<int> BulkCopyAsync<T>(IEnumerable<T> insertDatas, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
int result = 0;
// 异步分页处理大数据量插入
await db.Utilities.PageEachAsync(insertDatas, pageSize, async pageItems =>
{
// 异步调用批量插入API并累加结果
result += await questDbRestAPI.BulkCopyAsync(pageItems, dateFormat).ConfigureAwait(false);
result += await questDbRestAPI.BulkCopyAsync(pageItems, tableName, dateFormat).ConfigureAwait(false);
}).ConfigureAwait(false);
return result;
}

View File

@@ -7,16 +7,12 @@ namespace ThingsGateway.SqlSugar
/// <summary>
/// 绑定RestAPI需要的信息
/// </summary>
public static void SetRestApiInfo(DbConnectionStringBuilder builder, ref string host, ref string httpPort, ref string username, ref string password)
public static void SetRestApiInfo(DbConnectionStringBuilder builder, ref string host, ref string username, ref string password)
{
if (builder.TryGetValue("Host", out object hostValue))
{
host = Convert.ToString(hostValue);
}
if (builder.TryGetValue("HttpPort", out object httpPortValue))
{
httpPort = Convert.ToString(httpPortValue);
}
if (builder.TryGetValue("Username", out object usernameValue))
{
username = Convert.ToString(usernameValue);

View File

@@ -28,16 +28,16 @@ namespace ThingsGateway.SqlSugar
/// 初始化 QuestDbRestAPI 实例
/// </summary>
/// <param name="db">SqlSugar 数据库客户端</param>
public QuestDbRestAPI(ISqlSugarClient db)
/// <param name="httpPort">restApi端口</param>
public QuestDbRestAPI(ISqlSugarClient db, int httpPort = 9000)
{
var builder = new DbConnectionStringBuilder();
builder.ConnectionString = db.CurrentConnectionConfig.ConnectionString;
this.db = db;
string httpPort = String.Empty;
string host = String.Empty;
string username = String.Empty;
string password = String.Empty;
QuestDbRestAPHelper.SetRestApiInfo(builder, ref host, ref httpPort, ref username, ref password);
QuestDbRestAPHelper.SetRestApiInfo(builder, ref host, ref username, ref password);
BindHost(host, httpPort, username, password);
}
@@ -51,9 +51,14 @@ namespace ThingsGateway.SqlSugar
// HTTP GET 请求执行SQL
var result = string.Empty;
var url = $"{this.url}/exec?query={HttpUtility.UrlEncode(sql)}";
var request = new HttpRequestMessage(HttpMethod.Get, url);
if (!string.IsNullOrWhiteSpace(authorization))
client.DefaultRequestHeaders.Add("Authorization", authorization);
var httpResponseMessage = await client.GetAsync(url).ConfigureAwait(false);
{
request.Headers.Authorization = AuthenticationHeaderValue.Parse(authorization);
}
using var httpResponseMessage = await client.SendAsync(request).ConfigureAwait(false);
result = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
return result;
}
@@ -68,34 +73,34 @@ namespace ThingsGateway.SqlSugar
return ExecuteCommandAsync(sql).GetAwaiter().GetResult();
}
/// <summary>
/// 异步批量插入单条数据
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertData">要插入的数据</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>影响的行数</returns>
public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
if (db.CurrentConnectionConfig.MoreSettings == null)
db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings();
db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true;
var sql = db.InsertableT(insertData).ToSqlString();
var result = await ExecuteCommandAsync(sql).ConfigureAwait(false);
return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
}
///// <summary>
///// 异步批量插入单条数据
///// </summary>
///// <typeparam name="T">数据类型</typeparam>
///// <param name="insertData">要插入的数据</param>
///// <param name="dateFormat">日期格式字符串</param>
///// <returns>影响的行数</returns>
//public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
//{
// if (db.CurrentConnectionConfig.MoreSettings == null)
// db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings();
// db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true;
// var sql = db.InsertableT(insertData).ToSqlString();
// var result = await ExecuteCommandAsync(sql).ConfigureAwait(false);
// return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
//}
/// <summary>
/// 同步批量插入单条数据
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertData">要插入的数据</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>影响的行数</returns>
public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult();
}
///// <summary>
///// 同步批量插入单条数据
///// </summary>
///// <typeparam name="T">数据类型</typeparam>
///// <param name="insertData">要插入的数据</param>
///// <param name="dateFormat">日期格式字符串</param>
///// <returns>影响的行数</returns>
//public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
//{
// return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult();
//}
/// <summary>
/// 创建分页批量插入器
@@ -115,9 +120,10 @@ namespace ThingsGateway.SqlSugar
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertList">要插入的数据列表</param>
/// <param name="tableName">表名称</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>插入的记录数</returns>
public async Task<int> BulkCopyAsync<T>(List<T> insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
public async Task<int> BulkCopyAsync<T>(List<T> insertList, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
var result = 0;
var fileName = $"{Guid.NewGuid()}.csv";
@@ -126,35 +132,43 @@ namespace ThingsGateway.SqlSugar
{
// 准备多部分表单数据
var boundary = "---------------" + DateTime.Now.Ticks.ToString("x");
var list = new List<Hashtable>();
var name = db.EntityMaintenance.GetEntityInfo<T>().DbTableName;
tableName ??= db.EntityMaintenance.GetEntityInfo<T>().DbTableName;
// 获取或创建列信息缓存
var key = "QuestDbBulkCopy" + typeof(T).FullName + typeof(T).GetHashCode();
var columns = ReflectionInoCacheService.Instance.GetOrCreate(key, () =>
db.CopyNew().DbMaintenance.GetColumnInfosByTableName(name));
// 构建schema信息
columns.ForEach(d =>
db.CopyNew().DbMaintenance.GetColumnInfosByTableName(tableName));
var list = ReflectionInoCacheService.Instance.GetOrCreate($"{key}{dateFormat}List<Hashtable>", () =>
{
if (d.DataType == "TIMESTAMP")
var list = new List<Hashtable>();
// 构建schema信息
columns.ForEach(d =>
{
list.Add(new Hashtable()
if (d.DataType == "TIMESTAMP")
{
list.Add(new Hashtable()
{
{ "name", d.DbColumnName },
{ "type", d.DataType },
{ "pattern", dateFormat}
});
}
else
{
list.Add(new Hashtable()
}
else
{
list.Add(new Hashtable()
{
{ "name", d.DbColumnName },
{ "type", d.DataType }
});
}
});
}
});
return list;
}
);
var schema = JsonConvert.SerializeObject(list);
// 写入CSV文件
@@ -170,8 +184,8 @@ namespace ThingsGateway.SqlSugar
// 准备HTTP请求内容
using var httpContent = new MultipartFormDataContent(boundary);
using var fileStream = File.OpenRead(filePath);
if (!string.IsNullOrWhiteSpace(this.authorization))
client.DefaultRequestHeaders.Add("Authorization", this.authorization);
//if (!string.IsNullOrWhiteSpace(this.authorization))
// client.DefaultRequestHeaders.Add("Authorization", this.authorization);
httpContent.Add(new StringContent(schema), "schema");
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
@@ -183,8 +197,8 @@ namespace ThingsGateway.SqlSugar
"multipart/form-data; boundary=" + boundary);
// 发送请求并处理响应
var httpResponseMessage =
await Post(client, name, httpContent).ConfigureAwait(false);
using var httpResponseMessage =
await Post(client, tableName, httpContent).ConfigureAwait(false);
var readAsStringAsync = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync);
@@ -266,11 +280,12 @@ namespace ThingsGateway.SqlSugar
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertList">要插入的数据列表</param>
/// <param name="tableName">表名称</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>插入的记录数</returns>
public int BulkCopy<T>(List<T> insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
public int BulkCopy<T>(List<T> insertList, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
return BulkCopyAsync(insertList, dateFormat).GetAwaiter().GetResult();
return BulkCopyAsync(insertList, tableName, dateFormat).GetAwaiter().GetResult();
}
/// <summary>
@@ -280,7 +295,7 @@ namespace ThingsGateway.SqlSugar
/// <param name="httpPort">HTTP端口</param>
/// <param name="username">用户名</param>
/// <param name="password">密码</param>
private void BindHost(string host, string httpPort, string username, string password)
private void BindHost(string host, int httpPort, string username, string password)
{
url = host;
if (url.EndsWith('/'))

View File

@@ -2,9 +2,9 @@
{
public static class QuestDbSqlSugarClientExtensions
{
public static QuestDbRestAPI RestApi(this ISqlSugarClient db)
public static QuestDbRestAPI RestApi(this ISqlSugarClient db, int httpPort = 9000)
{
return new QuestDbRestAPI(db);
return new QuestDbRestAPI(db, httpPort);
}
}
}

View File

@@ -8,6 +8,6 @@
V Get<V>(string key);
IEnumerable<string> GetAllKey<V>();
void Remove<V>(string key);
V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue);
V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = 3600);
}
}

View File

@@ -31,7 +31,7 @@ namespace ThingsGateway.SqlSugar
return ReflectionInoCore<V>.GetInstance().GetAllKey();
}
public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue)
public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = 3600)
{
return ReflectionInoCore<V>.GetInstance().GetOrCreate(cacheKey, create);
}
@@ -43,10 +43,13 @@ namespace ThingsGateway.SqlSugar
}
public class ReflectionInoCore<V>
{
private MemoryCache InstanceCache => new MemoryCache() { Expire = 60 };
private MemoryCache InstanceCache = new MemoryCache() { Expire = 180 };
private static ReflectionInoCore<V> _instance = null;
private static readonly object _instanceLock = new object();
private ReflectionInoCore() { }
private ReflectionInoCore()
{
}
public V this[string key]
{
@@ -107,10 +110,10 @@ namespace ThingsGateway.SqlSugar
return this.InstanceCache.Keys;
}
public V GetOrCreate(string cacheKey, Func<V> create)
public V GetOrCreate(string cacheKey, Func<V> create, int expire = 3600)
{
return InstanceCache.GetOrAdd<V>(cacheKey, (a) =>
create());
create(), expire);
}
}
public static class ReflectionInoHelper

View File

@@ -447,6 +447,28 @@ namespace ThingsGateway.SqlSugar
}
public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true)
{
if (string.IsNullOrEmpty(tableName)) return new List<DbColumnInfo>();
string cacheKey = "QuestDB.GetColumnInfosByTableName." + this.SqlBuilder.GetNoTranslationColumnName(tableName).ToLower() + this.Context.CurrentConnectionConfig.ConfigId;
cacheKey = GetCacheKey(cacheKey);
if (isCache)
{
return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
{
return GetColInfo(tableName);
});
}
else
{
return GetColInfo(tableName);
}
}
private List<DbColumnInfo> GetColInfo(string tableName)
{
var sql = String.Format(GetColumnInfosByTableNameSql, tableName);
List<DbColumnInfo> result = new List<DbColumnInfo>();

View File

@@ -717,8 +717,32 @@ namespace ThingsGateway.SqlSugar
/// <returns>列信息列表</returns>
public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true)
{
var sql = $"select * from {this.SqlBuilder.GetTranslationColumnName(tableName)} where 1=2 ";
if (string.IsNullOrEmpty(tableName)) return new List<DbColumnInfo>();
string cacheKey = "TDengine.GetColumnInfosByTableName." + this.SqlBuilder.GetNoTranslationColumnName(tableName).ToLower() + this.Context.CurrentConnectionConfig.ConfigId;
cacheKey = GetCacheKey(cacheKey);
if (isCache)
{
return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
{
return GetColInfo(tableName);
});
}
else
{
return GetColInfo(tableName);
}
}
private List<DbColumnInfo> GetColInfo(string tableName)
{
List<DbColumnInfo> result = new List<DbColumnInfo>();
var sql = $"select * from {this.SqlBuilder.GetTranslationColumnName(tableName)} where 1=2 ";
DataTable dt = null;
try
{

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
@@ -22,18 +23,18 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SqlSugarCore.Dm" Version="8.8.0" />
<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.728" />
<PackageReference Include="SqlSugarCore.Dm" Version="8.8.1" />
<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.821" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.20" />
<!--<PackageReference Include="Microsoft.Data.Sqlite" Version="$(NET9Version)" />-->
<PackageReference Include="MySqlConnector" Version="2.4.0" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="TDengine.Connector" Version="3.1.7" />
<PackageReference Include="TDengine.Connector" Version="3.1.8" />
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.21" />
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.23" />
<PackageReference Include="System.Data.Common" Version="4.3.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="System.Formats.Asn1" Version="8.0.2" />

View File

@@ -1,11 +1,11 @@
<Project>
<PropertyGroup>
<PluginVersion>10.10.4</PluginVersion>
<ProPluginVersion>10.10.4</ProPluginVersion>
<DefaultVersion>10.10.7</DefaultVersion>
<AuthenticationVersion>10.10.1</AuthenticationVersion>
<SourceGeneratorVersion>10.10.1</SourceGeneratorVersion>
<PluginVersion>10.11.10</PluginVersion>
<ProPluginVersion>10.11.10</ProPluginVersion>
<DefaultVersion>10.11.10</DefaultVersion>
<AuthenticationVersion>10.11.2</AuthenticationVersion>
<SourceGeneratorVersion>10.11.2</SourceGeneratorVersion>
<NET8Version>8.0.19</NET8Version>
<NET9Version>9.0.8</NET9Version>
<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
@@ -28,7 +28,8 @@
<AnalysisModeStyle>None</AnalysisModeStyle>
<NoWarn>
CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;NU5104;NU1903;NU1902;CA1863;CA1812;CA1805;CA1515;CA1508;CA1819;CA1852;CA5394;CA1822;CA1815;CA1813;CA2000;CA5358;CA5384;CA5400;CA5401;CA1814;CA1835;CA5392;CA5350;CA2100;CA1848;CA1810;CA1513;CA5351;CA1510;CA1512;CA1823;NETSDK1206
CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;NU5104;NU1903;NU1902;CA1863;CA1812;CA1805;CA1515;CA1508;CA1819;CA1852;CA5394;CA1822;CA1815;CA1813;CA2000;CA5358;CA5384;CA5400;CA5401;CA1814;CA1835;CA5392;CA5350;CA2100;CA1848;CA1810;CA1513;CA5351;CA1510;CA1512;CA1823;RCS1102;RCS1194;NETSDK1206
</NoWarn>
<TargetFrameworks>net8.0;</TargetFrameworks>
<LangVersion>13.0</LangVersion>

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -4,10 +4,11 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CS-Script" Version="4.10.1" />
<PackageReference Include="CS-Script" Version="4.11.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -89,7 +89,7 @@ public partial class ChannelComponent : ComponentBase
await Channel.SetupAsync(config);
}
await Channel.ConnectAsync(Channel.ChannelOptions.ConnectTimeout, default);
await Channel.ConnectAsync(default);
if (OnConnectClick.HasDelegate)
await OnConnectClick.InvokeAsync(Channel);

View File

@@ -23,7 +23,7 @@
<EditorItem @bind-Field=Model.EncodingName>
<EditTemplate Context="value">
<div class="col-12 col-sm-4">
<Select @bind-Value=value.EncodingName Items="EncodingItems" />
<Select @bind-Value=value.EncodingName Items="EncodingItems" IsClearable/>
</div>
</EditTemplate>
</EditorItem>

View File

@@ -31,6 +31,6 @@ public partial class ConverterConfigComponent : ComponentBase
protected override void OnInitialized()
{
BoolItems = LocalizerUtil.GetBoolItems(Model.GetType(), nameof(Model.VariableStringLength), true);
EncodingItems = new List<SelectedItem>() { new SelectedItem("", "none") }.Concat(Encoding.GetEncodings().Select(a => new SelectedItem(a.CodePage.ToString(), a.DisplayName))).ToList();
EncodingItems = Encoding.GetEncodings().Select(a => new SelectedItem(a.CodePage.ToString(), a.DisplayName)).ToList();
}
}

View File

@@ -77,7 +77,8 @@ public partial class LogConsole : IDisposable
[Inject]
private ToastService ToastService { get; set; }
[Inject]
ITextFileReadService TextFileReadService { get; set; }
public void Dispose()
{
Disposed = true;
@@ -94,7 +95,7 @@ public partial class LogConsole : IDisposable
if (LogPath != null)
{
var files = TextFileReader.GetFiles(LogPath);
var files = await TextFileReadService.GetLogFilesAsync(LogPath);
if (!files.IsSuccess)
{
Messages = new List<LogMessage>();
@@ -105,7 +106,7 @@ public partial class LogConsole : IDisposable
await Task.Run(async () =>
{
Stopwatch sw = Stopwatch.StartNew();
var result = TextFileReader.LastLog(files.Content.FirstOrDefault());
var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault());
if (result.IsSuccess)
{
Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList();
@@ -143,7 +144,7 @@ public partial class LogConsole : IDisposable
{
if (LogPath != null)
{
var files = TextFileReader.GetFiles(LogPath);
var files = await TextFileReadService.GetLogFilesAsync(LogPath);
if (files.IsSuccess)
{
foreach (var item in files.Content)

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Razor;
namespace ThingsGateway.Debug;
public class ValueTransformConfig
{

View File

@@ -1,9 +1,5 @@
@namespace ThingsGateway.Gateway.Razor
@using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor
@namespace ThingsGateway.Debug
@using ThingsGateway.Foundation
@using ThingsGateway.Gateway.Application
@inherits ComponentDefault
<ValidateForm class="p-4 h-100" Model="@ValueTransformConfig" OnValidSubmit="OnSave">
<EditorForm AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=1 LabelWidth=150 Model="ValueTransformConfig">

View File

@@ -14,7 +14,7 @@ using System.Text.RegularExpressions;
using ThingsGateway.NewLife.Extension;
namespace ThingsGateway.Gateway.Razor;
namespace ThingsGateway.Debug;
public partial class ValueTransformConfigPage
{
@@ -205,5 +205,10 @@ public partial class ValueTransformConfigPage
}
}
[Inject]
ToastService ToastService { get; set; }
[Inject]
IStringLocalizer<ThingsGateway.Razor._Imports> RazorLocalizer { get; set; }
#endregion
}

View File

@@ -1,4 +1,23 @@
{
"ThingsGateway.Debug.ValueTransformType": {
"None": "None",
"Linear": "Linear",
"Sqrt": "Sqrt"
},
"ThingsGateway.Debug.ValueTransformConfig": {
"TransformType": "TransformType",
"MinMax": "MinMax",
"ClampToRawRange": "ClampToRawRange",
"DecimalPlaces": "DecimalPlaces",
"RawMin": "RawMin",
"RawMax": "RawMax",
"ActualMin": "ActualMin",
"ActualMax": "ActualMax"
},
"ThingsGateway.Debug.ChannelComponent": {
"BaudRate": "Baud Rate",
"BindUrl": "Local Bind IP Address",

View File

@@ -1,4 +1,21 @@
{
"ThingsGateway.Debug.ValueTransformType": {
"None": "无",
"Linear": "线性",
"Sqrt": "开方"
},
"ThingsGateway.Debug.ValueTransformConfig": {
"TransformType": "转换方式",
"MinMax": "最小最大值",
"ClampToRawRange": "限制范围",
"DecimalPlaces": "保留小数位",
"RawMin": "原始最小值",
"RawMax": "原始最大值",
"ActualMin": "实际最小值",
"ActualMax": "实际最大值"
},
"ThingsGateway.Debug.ChannelComponent": {
"BaudRate": "波特率",
"BindUrl": "本地url",

View File

@@ -26,7 +26,7 @@ public class PlatformService : IPlatformService
public async Task OnLogExport(string logPath)
{
var files = TextFileReader.GetFiles(logPath);
var files = TextFileReader.GetLogFilesAsync(logPath);
if (!files.IsSuccess)
{
return;
@@ -40,7 +40,7 @@ public class PlatformService : IPlatformService
await using var jSObject = await JSRuntime.InvokeAsync<IJSObjectReference>("import", $"{WebsiteConst.DefaultResourceUrl}js/downloadFile.js");
var path = Path.GetRelativePath("wwwroot", item);
string fileName = DateTime.Now.ToFileDateTimeFormat();
await jSObject.InvokeVoidAsync("blazor_downloadFile", url, fileName, new { FileName = path });
await jSObject.InvokeAsync<bool>("blazor_downloadFile", url, fileName, new { FileName = path });
}
}
}

View File

@@ -33,5 +33,6 @@ public class Startup : AppStartup
}
services.AddScoped<IPlatformService, PlatformService>();
services.AddSingleton<ITextFileReadService, TextFileReadService>();
}
}

View File

@@ -4,6 +4,7 @@
<Import Project="..\..\PackNuget.props" />
<PropertyGroup>
<TargetFrameworks>net8.0;</TargetFrameworks>
<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>-->
</PropertyGroup>
<ItemGroup>

View File

@@ -5,6 +5,7 @@
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<NoPackageAnalysis>true</NoPackageAnalysis>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<ItemGroup>

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;</TargetFrameworks>
<Version>$(SourceGeneratorVersion)</Version>
</PropertyGroup>
<ItemGroup>

View File

@@ -8,6 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Buffers;
namespace ThingsGateway.Foundation;
/// <summary>
@@ -16,12 +18,12 @@ namespace ThingsGateway.Foundation;
public abstract class DDPMessage : MessageBase, IResultMessage
{
/// <inheritdoc/>
public override int HeaderLength => 4;
public override long HeaderLength => 4;
public byte Type = 0;
public string Id;
public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock)
{
Id = byteBlock.ToString(byteBlock.Position, 11).Replace("\0", "");
Id = byteBlock.ToString(byteBlock.BytesRead, 11).Replace("\0", "");
OperCode = 0;
Content = GetContent(ref byteBlock);
@@ -44,31 +46,31 @@ public abstract class DDPMessage : MessageBase, IResultMessage
}
}
public abstract int GetBodyLength<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
public abstract byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
public abstract long GetBodyLength<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader;
public abstract byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader;
}
public class DDPTcpMessage : DDPMessage
{
public override int GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
public override long GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
{
return ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big) - 4;
}
public override byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock)
{
return byteBlock.Span.Slice(byteBlock.Position + 11, BodyLength - 12).ToArray();
return byteBlock.TotalSequence.Slice(byteBlock.BytesRead + 11, BodyLength - 12).ToArray();
}
}
public class DDPUdpMessage : DDPMessage
{
public override int GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
public override long GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
{
return byteBlock.Length - 4;
return (byteBlock.BytesRead + byteBlock.BytesRemaining - 4);
}
public override byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock)
{
return byteBlock.Span.Slice(byteBlock.Position + 12, BodyLength - 12).ToArray();
return byteBlock.TotalSequence.Slice(byteBlock.BytesRead + 12, BodyLength - 12).ToArray();
}
}

View File

@@ -25,6 +25,7 @@ public class DDPSend : ISendMessage
string Id;
byte Command;
bool Tcp;
public DDPSend(ReadOnlyMemory<byte> readOnlyMemory, string id, bool tcp, byte command = 0x89)
{
Tcp = tcp;
@@ -32,7 +33,8 @@ public class DDPSend : ISendMessage
Id = id;
Command = command;
}
public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockWriter
public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesWriter
{
WriterExtension.WriteValue(ref byteBlock, (byte)0x7b);
WriterExtension.WriteValue(ref byteBlock, (byte)Command);

View File

@@ -10,8 +10,6 @@
using System.Runtime.CompilerServices;
using ThingsGateway.NewLife;
using TouchSocket.Resources;
namespace ThingsGateway.Foundation;
@@ -33,50 +31,120 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
DDPAdapter.Config(Config);
}
// 将当前实例的日志记录器和加载回调设置到适配器中
DDPAdapter.Logger = Logger;
DDPAdapter.OnLoaded(this);
DDPAdapter.SendAsyncCallBack = DDPSendAsync;
DDPAdapter.ReceivedAsyncCallBack = DDPHandleReceivedData;
DataHandlingAdapter.SendAsyncCallBack = DefaultSendAsync;
return base.OnTcpConnected(e);
}
protected Task DefaultSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
{
return DDPAdapter.SendInputAsync(new DDPSend(memory, Id, true), cancellationToken);
}
protected Task DDPSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
{
return base.ProtectedDefaultSendAsync(memory, cancellationToken);
}
private DDPMessage DDPMessage { get; set; }
private Task DDPHandleReceivedData(IByteBlockReader byteBlock, IRequestInfo requestInfo)
#region
/// <summary>
/// 异步发送数据,通过适配器模式灵活处理数据发送。
/// </summary>
/// <param name="memory">待发送的只读字节内存块。</param>
/// <param name="token">可取消令箭</param>
/// <returns>一个异步任务,表示发送操作。</returns>
protected virtual async Task NewProtectedSendAsync(ReadOnlyMemory<byte> memory, CancellationToken token)
{
if (requestInfo is DDPMessage dDPMessage)
DDPMessage = dDPMessage;
this.ThrowIfDisposed();
this.ThrowIfClientNotConnected();
return EasyTask.CompletedTask;
}
private DeviceSingleStreamDataHandleAdapter<DDPTcpMessage> DDPAdapter = new();
private WaitLock _waitLock = new(nameof(DDPTcpSessionClientChannel));
if (!await this.OnTcpSending(memory).ConfigureAwait(false)) return;
protected override async ValueTask<bool> OnTcpReceiving(IByteBlockReader byteBlock)
{
DDPMessage? message = null;
var transport = this.Transport;
var adapter = this.DataHandlingAdapter;
var locker = transport.SemaphoreSlimForWriter;
await locker.WaitAsync(token).ConfigureAwait(false);
try
{
await _waitLock.WaitAsync().ConfigureAwait(false);
await DDPAdapter.ReceivedInputAsync(byteBlock).ConfigureAwait(false);
message = DDPMessage;
DDPMessage = null;
// 如果数据处理适配器未设置,则使用默认发送方式。
if (adapter == null)
{
await transport.Output.WriteAsync(memory, token).ConfigureAwait(false);
}
else
{
var byteBlock = new ByteBlock(1024);
var ddpSend = new DDPSend(memory, Id, true);
ddpSend.Build(ref byteBlock);
var newMemory = byteBlock.Memory;
var writer = new PipeBytesWriter(transport.Output);
adapter.SendInput(ref writer, in newMemory);
await writer.FlushAsync(token).ConfigureAwait(false);
}
}
finally
{
_waitLock.Release();
locker.Release();
}
}
/// <summary>
/// 异步发送请求信息的受保护方法。
///
/// 此方法首先检查当前对象是否能够发送请求信息,如果不能,则抛出异常。
/// 如果可以发送,它将使用数据处理适配器来异步发送输入请求。
/// </summary>
/// <param name="requestInfo">要发送的请求信息。</param>
/// <param name="token">可取消令箭</param>
/// <returns>返回一个任务,该任务代表异步操作的结果。</returns>
protected virtual async Task NewProtectedSendAsync(IRequestInfo requestInfo, CancellationToken token)
{
// 检查是否具备发送请求的条件,如果不具备则抛出异常
this.ThrowIfCannotSendRequestInfo();
this.ThrowIfDisposed();
this.ThrowIfClientNotConnected();
var transport = this.Transport;
var adapter = this.DataHandlingAdapter;
var locker = transport.SemaphoreSlimForWriter;
await locker.WaitAsync(token).ConfigureAwait(false);
try
{
var byteBlock = new ByteBlock(1024);
if (requestInfo is not IRequestInfoBuilder requestInfoBuilder)
{
throw new Exception();
}
requestInfoBuilder.Build(ref byteBlock);
var ddpSend = new DDPSend(byteBlock.Memory, Id, true);
var writer = new PipeBytesWriter(transport.Output);
adapter.SendInput(ref writer, ddpSend);
await writer.FlushAsync(token).ConfigureAwait(false);
}
finally
{
locker.Release();
}
}
#endregion
public override Task SendAsync(IRequestInfo requestInfo, CancellationToken token = default)
{
return NewProtectedSendAsync(requestInfo, token);
}
public override Task SendAsync(ReadOnlyMemory<byte> memory, CancellationToken token = default)
{
return NewProtectedSendAsync(memory, token);
}
private DeviceSingleStreamDataHandleAdapter<DDPTcpMessage> DDPAdapter = new();
protected override async ValueTask<bool> OnTcpReceiving(IBytesReader byteBlock)
{
if (DDPAdapter.TryParseRequest(ref byteBlock, out var message))
{
return true;
}
if (message != null)
@@ -90,11 +158,11 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
if (this.DataHandlingAdapter == null)
{
await this.OnTcpReceived(new ReceivedDataEventArgs(reader, default)).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await this.OnTcpReceived(new ReceivedDataEventArgs(message.Content, default)).ConfigureAwait(false);
}
else
{
await this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(false);
}
return true;
@@ -127,16 +195,16 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
}
}
await ResetIdAsync(id).ConfigureAwait(false);
await ResetIdAsync(id, ClosedToken).ConfigureAwait(false);
//发送成功
await DDPAdapter.SendInputAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), ClosedToken).ConfigureAwait(false);
await base.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), ClosedToken).ConfigureAwait(false);
if (log)
Logger?.Info(string.Format(AppResource.DtuConnected, Id));
}
else if (message.Type == 0x02)
{
await DDPAdapter.SendInputAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, Id, true, 0x82), ClosedToken).ConfigureAwait(false);
await base.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, Id, true, 0x82), ClosedToken).ConfigureAwait(false);
Logger?.Info(string.Format(AppResource.DtuDisconnecting, Id));
await Task.Delay(100).ConfigureAwait(false);
await this.CloseAsync().ConfigureAwait(false);

View File

@@ -36,16 +36,12 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
DDPAdapter.Config(Config);
}
// 将当前实例的日志记录器和加载回调设置到适配器中
DDPAdapter.Logger = Logger;
if (DDPAdapter.Owner != null)
{
DDPAdapter.OnLoaded(this);
}
DDPAdapter.SendCallBackAsync = DDPSendAsync;
DDPAdapter.ReceivedCallBack = DDPHandleReceivedData;
DDPAdapter.SendCallBackAsync = base.ProtectedDefaultSendAsync;
DataHandlingAdapter.SendCallBackAsync = DefaultSendAsync;
}
@@ -62,22 +58,7 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
}
}
protected Task DDPSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory, CancellationToken token)
{
//获取endpoint
return base.ProtectedDefaultSendAsync(endPoint, memory, token);
}
private ConcurrentDictionary<EndPoint, DDPMessage> DDPMessageDict { get; set; } = new();
private Task DDPHandleReceivedData(EndPoint endPoint, IByteBlockReader byteBlock, IRequestInfo requestInfo)
{
if (requestInfo is DDPMessage dDPMessage)
{
DDPMessageDict.AddOrUpdate(endPoint, dDPMessage);
}
return EasyTask.CompletedTask;
}
private DeviceUdpDataHandleAdapter<DDPUdpMessage> DDPAdapter = new();
@@ -98,27 +79,14 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
return base.StopAsync(token);
}
private ConcurrentDictionary<EndPoint, WaitLock> _waitLocks = new();
protected override async ValueTask<bool> OnUdpReceiving(UdpReceiveingEventArgs e)
{
var byteBlock = e.ByteBlock;
var byteBlock = e.Memory;
var endPoint = e.EndPoint;
DDPMessage? message = null;
var waitLock = _waitLocks.GetOrAdd(endPoint, new WaitLock(nameof(DDPUdpSessionChannel)));
try
{
await waitLock.WaitAsync().ConfigureAwait(false);
await DDPAdapter.ReceivedInput(endPoint, byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
if (DDPMessageDict.TryGetValue(endPoint, out var dDPMessage))
message = dDPMessage;
DDPMessageDict.TryRemove(endPoint, out _);
}
finally
{
waitLock.Release();
}
if (!DDPAdapter.TryParseRequest(endPoint, byteBlock, out var message))
return true;
if (message != null)
{
@@ -127,15 +95,13 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
var id = $"ID={message.Id}";
if (message.Type == 0x09)
{
var reader = new ByteBlockReader(message.Content);
if (this.DataHandlingAdapter == null)
{
await this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, reader, default)).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, message.Content, default)).ConfigureAwait(false);
}
else
{
await this.DataHandlingAdapter.ReceivedInput(endPoint, reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await this.DataHandlingAdapter.ReceivedInputAsync(endPoint, message.Content).ConfigureAwait(false);
}
return true;

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
namespace ThingsGateway.Foundation;
/// <summary>
@@ -59,15 +57,9 @@ public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient, IC
/// </summary>
public ChannelEventHandler Stoping { get; }
/// <summary>
/// 主动请求时的等待池
/// </summary>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; }
void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue);
}
/// <summary>

View File

@@ -22,6 +22,8 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn
/// </summary>
DataHandlingAdapter ReadOnlyDataHandlingAdapter { get; }
/// <summary>
/// 通道等待池
/// </summary>
@@ -34,4 +36,6 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn
/// </summary>
/// <param name="adapter">适配器</param>
void SetDataHandlingAdapter(DataHandlingAdapter adapter);
void SetDataHandlingAdapterLogger(ILog log);
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
using TouchSocket.SerialPorts;
@@ -31,13 +29,23 @@ public class OtherChannel : SetupConfigObject, IClientChannel
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter == null && ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
}
}
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
pool?.CancelAll();
pool?.SafeDispose();
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
@@ -65,22 +73,25 @@ public class OtherChannel : SetupConfigObject, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new();
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
//private readonly WaitLock _connectLock = new WaitLock();
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
}
/// <summary>
/// 设置数据处理适配器。
@@ -104,20 +115,17 @@ public class OtherChannel : SetupConfigObject, IClientChannel
}
// 设置适配器的日志记录器和加载、接收数据的回调方法。
adapter.Logger = Logger;
adapter.OnLoaded(this);
adapter.ReceivedAsyncCallBack = PrivateHandleReceivedData;
//adapter.SendCallBack = this.ProtectedDefaultSend;
adapter.SendAsyncCallBack = ProtectedDefaultSendAsync;
// 将提供的适配器实例设置为当前实例的数据处理适配器。
m_dataHandlingAdapter = adapter;
}
private async Task PrivateHandleReceivedData(IByteBlockReader byteBlock, IRequestInfo requestInfo)
private Task PrivateHandleReceivedData(ReadOnlyMemory<byte> byteBlock, IRequestInfo requestInfo)
{
LastReceivedTime = DateTime.Now;
await this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived).ConfigureAwait(false);
return this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived);
}
/// <summary>
@@ -154,7 +162,8 @@ public class OtherChannel : SetupConfigObject, IClientChannel
return Task.FromResult(Result.Success);
}
public volatile bool online;
public Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
public Task ConnectAsync(CancellationToken token)
{
var cts = m_transport;
m_transport = new();
@@ -180,8 +189,11 @@ public class OtherChannel : SetupConfigObject, IClientChannel
}
else
{
// 否则,使用适配器的发送方法进行数据发送。
return m_dataHandlingAdapter.SendInputAsync(memory, cancellationToken);
var byteBlock = new ByteBlock(1024);
m_dataHandlingAdapter.SendInput(ref byteBlock, memory);
byteBlock.SafeDispose();
return EasyTask.CompletedTask;
}
}
@@ -190,9 +202,14 @@ public class OtherChannel : SetupConfigObject, IClientChannel
// 检查是否具备发送请求的条件,如果不具备则抛出异常
ThrowIfCannotSendRequestInfo();
// 使用数据处理适配器异步发送输入请求
return m_dataHandlingAdapter.SendInputAsync(requestInfo, cancellationToken);
var byteBlock = new ByteBlock(1024);
m_dataHandlingAdapter.SendInput(ref byteBlock, requestInfo);
byteBlock.SafeDispose();
return EasyTask.CompletedTask;
}
private void ThrowIfCannotSendRequestInfo()
{
if (m_dataHandlingAdapter?.CanSendRequestInfo != true)
@@ -200,4 +217,6 @@ public class OtherChannel : SetupConfigObject, IClientChannel
throw new NotSupportedException($"当前适配器为空或者不支持对象发送。");
}
}
}

View File

@@ -59,19 +59,18 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
public bool DtuIdHex { get; set; }
/// <inheritdoc/>
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e)
{
var len = HeartbeatByte.Length;
if (client is TcpSessionClientChannel socket && socket.Service is ITcpServiceChannel tcpServiceChannel)
{
if (!socket.Id.StartsWith("ID="))
{
var id = DtuIdHex ? $"ID={e.ByteBlock.Span.ToHexString()}" : $"ID={e.ByteBlock.ToString(0, e.ByteBlock.Length)}";
var id = DtuIdHex ? $"ID={e.Reader.ToHexString()}" : $"ID={e.Reader.ToString()}";
if (tcpServiceChannel.TryGetClient(id, out var oldClient))
{
try
{
//await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
await oldClient.CloseAsync().ConfigureAwait(false);
oldClient.Dispose();
}
@@ -79,7 +78,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
{
}
}
await socket.ResetIdAsync(id).ConfigureAwait(false);
await socket.ResetIdAsync(id, client.ClosedToken).ConfigureAwait(false);
client.Logger?.Info(string.Format(AppResource.DtuConnected, id));
e.Handled = true;
}
@@ -88,7 +87,6 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
{
try
{
//await socket.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
await socket.CloseAsync().ConfigureAwait(false);
socket.Dispose();
}
@@ -102,11 +100,11 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
if (len > 0)
{
if (HeartbeatByte.Span.SequenceEqual(e.ByteBlock.Memory.Slice(0, len).Span))
if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, len).First.Span))
{
if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200))
{
await Task.Delay(200).ConfigureAwait(false);
await Task.Delay(200, client.ClosedToken).ConfigureAwait(false);
}
//回应心跳包
await socket.SendAsync(HeartbeatByte, socket.ClosedToken).ConfigureAwait(false);
@@ -118,4 +116,6 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
}
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
}
}

View File

@@ -105,7 +105,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
}
}
}
private Task Task;
private Task _task;
private bool SendHeartbeat;
public int HeartbeatTime { get; set; } = 3000;
@@ -125,17 +125,17 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
{
await tcpClient.SendAsync(DtuIdByte, tcpClient.ClosedToken).ConfigureAwait(false);
if (Task == null)
if (_task == null)
{
Task = Task.Factory.StartNew(async () =>
_task = Task.Run(async () =>
{
var failedCount = 0;
while (SendHeartbeat)
{
await Task.Delay(HeartbeatTime).ConfigureAwait(false);
await Task.Delay(HeartbeatTime, client.ClosedToken).ConfigureAwait(false);
if (!client.Online)
{
continue;
break;
}
try
@@ -159,15 +159,15 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
}
}
Task = null;
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
_task = null;
});
}
}
await e.InvokeNext().ConfigureAwait(false);
}
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e)
{
if (client is ITcpSessionClient)
{
@@ -181,7 +181,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
var len = HeartbeatByte.Length;
if (len > 0)
{
if (HeartbeatByte.Span.SequenceEqual(e.ByteBlock.Memory.Slice(0, len).Span))
if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, len).First.Span))
{
e.Handled = true;
}
@@ -190,6 +190,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
}
}
public Task OnTcpClosed(ITcpSession client, ClosedEventArgs e)
{
SendHeartbeat = false;

View File

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

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
using TouchSocket.SerialPorts;
@@ -34,7 +32,6 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
pool?.CancelAll();
pool?.SafeDispose();
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
@@ -50,6 +47,26 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
/// <inheritdoc/>
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter == null && ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
}
/// <inheritdoc/>
public ChannelEventHandler Started { get; } = new();
@@ -65,14 +82,13 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new();
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
//private readonly WaitLock _connectLock = new WaitLock();
/// <inheritdoc/>
@@ -86,6 +102,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
if (Online)
{
PortName = null;
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
if (!Online)
{
@@ -103,7 +120,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
}
/// <inheritdoc/>
public override async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
public override async Task ConnectAsync(CancellationToken token)
{
if (!Online)
{
@@ -119,7 +136,8 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
if (port != null)
PortName = $"{port.PortName}";
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
await this.OnChannelEvent(Starting).ConfigureAwait(false);
await base.ConnectAsync(token).ConfigureAwait(false);
if (Online)
{
if (token.IsCancellationRequested) return;
@@ -134,12 +152,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
}
private string PortName { get; set; }
/// <inheritdoc/>
public override string? ToString()
@@ -153,54 +166,43 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
return base.ToString();
}
protected override async Task OnSerialClosed(ClosedEventArgs e)
protected override Task OnSerialClosed(ClosedEventArgs e)
{
Logger?.Info($"{ToString()} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await base.OnSerialClosed(e).ConfigureAwait(false);
return base.OnSerialClosed(e);
}
/// <inheritdoc/>
protected override async Task OnSerialClosing(ClosingEventArgs e)
protected override Task OnSerialClosing(ClosingEventArgs e)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
Logger?.Trace($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await base.OnSerialClosing(e).ConfigureAwait(false);
return base.OnSerialClosing(e);
}
/// <inheritdoc/>
protected override async Task OnSerialConnecting(ConnectingEventArgs e)
protected override Task OnSerialConnecting(ConnectingEventArgs e)
{
Logger?.Trace($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await this.OnChannelEvent(Starting).ConfigureAwait(false);
await base.OnSerialConnecting(e).ConfigureAwait(false);
return base.OnSerialConnecting(e);
}
protected override async Task OnSerialConnected(ConnectedEventArgs e)
protected override Task OnSerialConnected(ConnectedEventArgs e)
{
Logger?.Debug($"{ToString()} Connected");
await base.OnSerialConnected(e).ConfigureAwait(false);
return base.OnSerialConnected(e);
}
/// <inheritdoc/>
protected override async Task OnSerialReceived(ReceivedDataEventArgs e)
{
await base.OnSerialReceived(e).ConfigureAwait(false);
if (e.RequestInfo is MessageBase response)
{
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
{
await func.Invoke(this, e, ChannelReceived.Count == 1).ConfigureAwait(false);
e.Handled = true;
}
}
if (e.Handled)
return;
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override void SafetyDispose(bool disposing)
{
WaitHandlePool.SafeDispose();
WaitHandlePool?.CancelAll();
base.SafetyDispose(disposing);
}
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
namespace ThingsGateway.Foundation;
@@ -31,9 +29,27 @@ public class TcpClientChannel : TcpClient, IClientChannel
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
pool?.CancelAll();
pool?.SafeDispose();
}
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter == null && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
@@ -62,14 +78,13 @@ public class TcpClientChannel : TcpClient, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new();
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;
/// <inheritdoc/>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
//private readonly WaitLock _connectLock = new WaitLock();
/// <inheritdoc/>
@@ -82,6 +97,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
//await _connectLock.WaitAsync().ConfigureAwait(false);
if (Online)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
if (!Online)
{
@@ -99,7 +115,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
}
/// <inheritdoc/>
public override async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
public override async Task ConnectAsync(CancellationToken token)
{
if (!Online)
{
@@ -109,7 +125,8 @@ public class TcpClientChannel : TcpClient, IClientChannel
if (!Online)
{
if (token.IsCancellationRequested) return;
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
await this.OnChannelEvent(Starting).ConfigureAwait(false);
await base.ConnectAsync(token).ConfigureAwait(false);
if (Online)
{
if (token.IsCancellationRequested) return;
@@ -124,12 +141,6 @@ public class TcpClientChannel : TcpClient, IClientChannel
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
}
/// <inheritdoc/>
public override string ToString()
@@ -137,48 +148,39 @@ public class TcpClientChannel : TcpClient, IClientChannel
return $"{IP}:{Port}";
}
protected override async Task OnTcpClosed(ClosedEventArgs e)
protected override Task OnTcpClosed(ClosedEventArgs e)
{
Logger?.Info($"{ToString()} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await base.OnTcpClosed(e).ConfigureAwait(false);
return base.OnTcpClosed(e);
}
/// <inheritdoc/>
protected override async Task OnTcpClosing(ClosingEventArgs e)
protected override Task OnTcpClosing(ClosingEventArgs e)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
Logger?.Trace($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await base.OnTcpClosing(e).ConfigureAwait(false);
return base.OnTcpClosing(e);
}
/// <inheritdoc/>
protected override async Task OnTcpConnecting(ConnectingEventArgs e)
protected override Task OnTcpConnecting(ConnectingEventArgs e)
{
Logger?.Trace($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
await this.OnChannelEvent(Starting).ConfigureAwait(false);
await base.OnTcpConnecting(e).ConfigureAwait(false);
return base.OnTcpConnecting(e);
}
protected override async Task OnTcpConnected(ConnectedEventArgs e)
protected override Task OnTcpConnected(ConnectedEventArgs e)
{
Logger?.Info($"{ToString()} Connected");
await base.OnTcpConnected(e).ConfigureAwait(false);
return base.OnTcpConnected(e);
}
/// <inheritdoc/>
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
{
await base.OnTcpReceived(e).ConfigureAwait(false);
if (e.RequestInfo is MessageBase response)
{
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
{
await func.Invoke(this, e, ChannelReceived.Count == 1).ConfigureAwait(false);
e.Handled = true;
}
}
if (e.Handled)
return;
@@ -188,7 +190,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
/// <inheritdoc/>
protected override void SafetyDispose(bool disposing)
{
WaitHandlePool.SafeDispose();
WaitHandlePool?.CancelAll();
base.SafetyDispose(disposing);
}
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
namespace ThingsGateway.Foundation;
@@ -23,30 +21,6 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
/// <inheritdoc/>
public ConcurrentList<IDevice> Collects { get; } = new();
///// <summary>
///// 停止时是否发送ShutDown
///// </summary>
//public bool ShutDownEnable { get; set; } = true;
/// <inheritdoc/>
public override async Task ClearAsync()
{
foreach (var client in Clients)
{
try
{
//if (ShutDownEnable)
// await client.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
await client.CloseAsync().ConfigureAwait(false);
client.SafeDispose();
}
catch
{
}
}
}
public async Task ClientDisposeAsync(string id)
{
if (this.TryGetClient(id, out var client))
@@ -136,6 +110,7 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
{
m_transport?.SafeCancel();
m_transport?.SafeDispose();
m_transport = null;
base.SafetyDispose(disposing);
}
/// <inheritdoc/>
@@ -209,7 +184,7 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
}
/// <inheritdoc/>
public Task ConnectAsync(int timeout = 3000, CancellationToken token = default)
public Task ConnectAsync(CancellationToken token = default)
{
if (token.IsCancellationRequested)
return EasyTask.CompletedTask;
@@ -268,22 +243,13 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
{
await base.OnTcpReceived(socketClient, e).ConfigureAwait(false);
if (e.RequestInfo is MessageBase response)
{
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
{
await func.Invoke(socketClient, e, ChannelReceived.Count == 1).ConfigureAwait(false);
e.Handled = true;
}
}
if (e.Handled)
return;
await socketClient.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}
/// <inheritdoc/>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
IEnumerable<TcpSessionClientChannel> ITcpServiceChannel.Clients => base.Clients;

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
namespace ThingsGateway.Foundation;
@@ -23,13 +21,31 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
public TcpSessionClientChannel()
{
}
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter == null && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
}
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
pool?.CancelAll();
pool?.SafeDispose();
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
@@ -60,7 +76,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new();
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new(0, ushort.MaxValue);
/// <inheritdoc/>
public WaitLock WaitLock { get; internal set; } = new(nameof(TcpSessionClientChannel));
@@ -69,25 +85,19 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
/// <inheritdoc/>
public override Task<Result> CloseAsync(string msg, CancellationToken token)
{
WaitHandlePool.SafeDispose();
WaitHandlePool?.CancelAll();
return base.CloseAsync(msg, token);
}
/// <inheritdoc/>
public Task ConnectAsync(int timeout, CancellationToken token) => Task.CompletedTask;
public Task ConnectAsync(CancellationToken token) => Task.CompletedTask;
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
}
/// <inheritdoc/>
public Task SetupAsync(TouchSocketConfig config) => Task.CompletedTask;
/// <inheritdoc/>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
/// <inheritdoc/>
public override string ToString()
@@ -98,7 +108,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
/// <inheritdoc/>
protected override void SafetyDispose(bool disposing)
{
WaitHandlePool.SafeDispose();
WaitHandlePool?.CancelAll();
base.SafetyDispose(disposing);
}
@@ -136,14 +146,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
{
await base.OnTcpReceived(e).ConfigureAwait(false);
if (e.RequestInfo is MessageBase response)
{
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
{
await func.Invoke(this, e, ChannelReceived.Count == 1).ConfigureAwait(false);
e.Handled = true;
}
}
if (e.Handled)
return;

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
namespace ThingsGateway.Foundation;
@@ -28,13 +26,31 @@ public class UdpSessionChannel : UdpSession, IClientChannel
ResetSign();
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter == null && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
SetAdapter(udpDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
}
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
pool?.CancelAll();
pool?.SafeDispose();
}
/// <inheritdoc/>
@@ -69,14 +85,13 @@ public class UdpSessionChannel : UdpSession, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; set; } = new();
public WaitHandlePool<MessageBase> WaitHandlePool { get; set; } = new(0, ushort.MaxValue);
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
/// <inheritdoc/>
public Task<Result> CloseAsync(string msg, CancellationToken token)
@@ -85,19 +100,14 @@ public class UdpSessionChannel : UdpSession, IClientChannel
}
/// <inheritdoc/>
public async Task ConnectAsync(int timeout = 3000, CancellationToken token = default)
public Task ConnectAsync(CancellationToken token = default)
{
if (token.IsCancellationRequested)
return;
await StartAsync().ConfigureAwait(false);
return EasyTask.CompletedTask; ;
return StartAsync();
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
SetAdapter(udpDataHandlingAdapter);
}
public CancellationToken ClosedToken => this.m_transport == null ? new CancellationToken(true) : this.m_transport.Token;
private CancellationTokenSource m_transport;
/// <inheritdoc/>
@@ -188,14 +198,6 @@ public class UdpSessionChannel : UdpSession, IClientChannel
{
await base.OnUdpReceived(e).ConfigureAwait(false);
if (e.RequestInfo is MessageBase response)
{
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
{
await func.Invoke(this, e, ChannelReceived.Count == 1).ConfigureAwait(false);
e.Handled = true;
}
}
if (e.Handled)
return;
@@ -207,7 +209,8 @@ public class UdpSessionChannel : UdpSession, IClientChannel
{
m_transport?.SafeCancel();
m_transport?.SafeDispose();
WaitHandlePool.SafeDispose();
m_transport = null;
WaitHandlePool?.CancelAll();
base.SafetyDispose(disposing);
}
}

View File

@@ -15,15 +15,18 @@ namespace ThingsGateway.Foundation;
/// <summary>
/// TCP/Serial适配器基类
/// </summary>
public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandlingAdapter<TRequest> where TRequest : MessageBase, new()
public class DeviceSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingAdapter<TRequest>, IDeviceDataHandleAdapter where TRequest : MessageBase, new()
{
public new ILog Logger { get; set; }
/// <inheritdoc cref="DeviceSingleStreamDataHandleAdapter{TRequest}"/>
public DeviceSingleStreamDataHandleAdapter()
{
CacheTimeoutEnable = true;
SurLength = int.MaxValue;
}
/// <inheritdoc/>
public override bool CanSendRequestInfo => true;
@@ -40,11 +43,11 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
public TRequest Request { get; set; }
/// <inheritdoc />
public void SetRequest(ISendMessage sendMessage, ref ValueByteBlock byteBlock)
public void SetRequest(ISendMessage sendMessage)
{
var request = GetInstance();
request.Sign = sendMessage.Sign;
request.SendInfo(sendMessage, ref byteBlock);
request.SendInfo(sendMessage);
Request = request;
}
@@ -55,24 +58,24 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
}
/// <inheritdoc />
protected override FilterResult Filter<TByteBlock>(ref TByteBlock byteBlock, bool beCached, ref TRequest request, ref int tempCapacity)
protected override FilterResult Filter<TReader>(ref TReader byteBlock, bool beCached, ref TRequest request)
{
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString(' ') : byteBlock.ToString(byteBlock.Position))}");
Logger?.Trace($"{ToString()}- Receive:{(IsHexLog ? byteBlock.ToHexString(byteBlock.BytesRead, ' ') : byteBlock.ToString(byteBlock.BytesRead))}");
try
{
if (IsSingleThread)
request = Request == null ? GetInstance() : Request;
request = Request == null ? Request = GetInstance() : Request;
else
{
if (!beCached)
request = GetInstance();
}
var pos = byteBlock.Position;
var pos = byteBlock.BytesRead;
if (request.HeaderLength > byteBlock.CanReadLength)
if (request.HeaderLength > byteBlock.BytesRemaining)
{
return FilterResult.Cache;//当头部都无法解析时,直接缓存
}
@@ -80,19 +83,18 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
//检查头部合法性
if (request.CheckHead(ref byteBlock))
{
byteBlock.Position = pos;
byteBlock.BytesRead = pos;
if (request.BodyLength > MaxPackageSize)
{
request.OperCode = -1;
request.ErrorMessage = $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={MaxPackageSize}";
OnError(default, request.ErrorMessage, true, true);
SetResult(request);
Reset();
Logger?.LogWarning($"{ToString()} {request.ErrorMessage}");
return FilterResult.GoOn;
}
if (request.BodyLength + request.HeaderLength > byteBlock.CanReadLength)
if (request.BodyLength + request.HeaderLength > byteBlock.BytesRemaining)
{
//body不满足解析开始缓存然后保存对象
tempCapacity = request.BodyLength + request.HeaderLength;
return FilterResult.Cache;
}
//if (request.BodyLength <= 0)
@@ -101,56 +103,47 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
// request.BodyLength = byteBlock.Length;
//}
var headPos = pos + request.HeaderLength;
byteBlock.Position = headPos;
byteBlock.BytesRead = headPos;
var result = request.CheckBody(ref byteBlock);
if (result == FilterResult.Cache)
{
byteBlock.BytesRead = pos;
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}-Received incomplete, cached message, current length:{byteBlock.Length} {request?.ErrorMessage}");
tempCapacity = request.BodyLength + request.HeaderLength;
Logger?.Trace($"{ToString()}-Received incomplete, cached message, need length:{request.HeaderLength + request.BodyLength} ,current length:{byteBlock.BytesRead + byteBlock.BytesRemaining} {request?.ErrorMessage}");
request.OperCode = -1;
}
else if (result == FilterResult.GoOn)
{
var addLen = request.HeaderLength + request.BodyLength;
byteBlock.Position = pos + (addLen > 0 ? addLen : 1);
Logger?.Trace($"{ToString()}-{request?.ToString()}");
byteBlock.BytesRead = pos + (addLen > 0 ? addLen : 1);
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}-{request?.ToString()}");
request.OperCode = -1;
SetResult(request);
}
else if (result == FilterResult.Success)
{
var addLen = request.HeaderLength + request.BodyLength;
byteBlock.Position = pos + (addLen > 0 ? addLen : 1);
byteBlock.BytesRead = pos + (addLen > 0 ? addLen : 1);
}
return result;
}
else
{
byteBlock.Position = pos + 1;//移动游标
byteBlock.BytesRead = pos + 1;//移动游标
request.OperCode = -1;
SetResult(request);
return FilterResult.GoOn;//放弃解析
}
}
catch (Exception ex)
{
Logger?.LogWarning(ex, $"{ToString()} Received parsing error");
byteBlock.Position = byteBlock.Length;//移动游标
byteBlock.BytesRead = byteBlock.BytesRead + byteBlock.BytesRemaining;//移动游标
request.Exception = ex;
request.OperCode = -1;
SetResult(request);
return FilterResult.GoOn;//放弃解析
}
}
private void SetResult(TRequest request)
{
if ((Owner as IClientChannel)?.WaitHandlePool?.TryGetDataAsync(request.Sign, out var waitDataAsync) == true)
{
waitDataAsync.SetResult(request);
}
}
/// <summary>
/// 获取泛型实例。
@@ -161,47 +154,32 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
return new TRequest() { OperCode = -1, Sign = -1 };
}
/// <inheritdoc/>
protected override void OnReceivedSuccess(TRequest request)
public override void SendInput<TWriter>(ref TWriter writer, in ReadOnlyMemory<byte> memory)
{
Request = null;
}
/// <inheritdoc />
protected override async Task PreviewSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString(' ') : (memory.Span.ToString(Encoding.UTF8)))}");
//发送
await GoSendAsync(memory, cancellationToken).ConfigureAwait(false);
writer.Write(memory.Span);
}
/// <inheritdoc/>
protected override async Task PreviewSendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken)
public override void SendInput<TWriter>(ref TWriter writer, IRequestInfo requestInfo)
{
if (!(requestInfo is ISendMessage sendMessage))
{
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
}
cancellationToken.ThrowIfCancellationRequested();
var byteBlock = new ValueByteBlock(sendMessage.MaxLength);
try
var span = writer.GetSpan(sendMessage.MaxLength);
sendMessage.Build(ref writer);
if (Logger?.LogLevel <= LogLevel.Trace)
{
sendMessage.Build(ref byteBlock);
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? byteBlock.Span.ToHexString(' ') : (byteBlock.Span.ToString(Encoding.UTF8)))}");
//非并发主从协议
if (IsSingleThread)
{
SetRequest(sendMessage, ref byteBlock);
}
await GoSendAsync(byteBlock.Memory, cancellationToken).ConfigureAwait(false);
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? span.Slice(0, (int)writer.WrittenCount).ToHexString(' ') : (span.Slice(0, (int)writer.WrittenCount).ToString(Encoding.UTF8)))}");
}
finally
//非并发主从协议
if (IsSingleThread)
{
byteBlock.SafeDispose();
SetRequest(sendMessage);
}
}
}

View File

@@ -16,8 +16,10 @@ namespace ThingsGateway.Foundation;
/// <summary>
/// UDP适配器基类
/// </summary>
public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where TRequest : MessageBase, new()
public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter, IDeviceDataHandleAdapter where TRequest : MessageBase, new()
{
public new ILog Logger { get; set; }
/// <inheritdoc/>
public override bool CanSendRequestInfo => true;
@@ -34,11 +36,11 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
public TRequest Request { get; set; }
/// <inheritdoc />
public void SetRequest(ISendMessage sendMessage, ref ValueByteBlock byteBlock)
public void SetRequest(ISendMessage sendMessage)
{
var request = GetInstance();
request.Sign = sendMessage.Sign;
request.SendInfo(sendMessage, ref byteBlock);
request.SendInfo(sendMessage);
Request = request;
}
@@ -57,113 +59,132 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
return new TRequest() { OperCode = -1, Sign = -1 };
}
/// <inheritdoc/>
protected override async Task PreviewReceived(EndPoint remoteEndPoint, IByteBlockReader byteBlock)
#region ParseRequest
/// <summary>
/// 尝试从字节读取器中解析出请求信息。
/// </summary>
/// <param name="remoteEndPoint">remoteEndPoint。</param>
/// <param name="memory">memory。</param>
/// <param name="request">解析出的请求信息。</param>
/// <returns>解析成功返回 true否则返回 false。</returns>
public bool TryParseRequest(EndPoint remoteEndPoint, ReadOnlyMemory<byte> memory, out TRequest request)
{
return this.ParseRequestCore(remoteEndPoint, memory, out request);
}
protected virtual bool ParseRequestCore(EndPoint remoteEndPoint, ReadOnlyMemory<byte> memory, out TRequest request1)
{
request1 = null;
try
{
byteBlock.Position = 0;
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{remoteEndPoint}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString(' ') : byteBlock.ToString(byteBlock.Position))}");
Logger?.Trace($"{remoteEndPoint}- Receive:{(IsHexLog ? memory.Span.ToHexString(' ') : memory.Span.ToString(Encoding.UTF8))}");
TRequest request = null;
if (IsSingleThread)
request = Request == null ? GetInstance() : Request;
request = Request == null ? Request = GetInstance() : Request;
else
{
request = GetInstance();
}
request1 = request;
var pos = byteBlock.Position;
var byteBlock = new ByteBlockReader(memory);
byteBlock.BytesRead = 0;
var pos = byteBlock.BytesRead;
if (request.HeaderLength > byteBlock.CanReadLength)
{
return;//当头部都无法解析时,直接缓存
return false;//当头部都无法解析时,直接缓存
}
//检查头部合法性
if (request.CheckHead(ref byteBlock))
{
byteBlock.Position = pos;
byteBlock.BytesRead = pos;
if (request.BodyLength > MaxPackageSize)
{
OnError(default, $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={MaxPackageSize}", true, true);
return;
request.OperCode = -1;
request.ErrorMessage = $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={MaxPackageSize}";
Reset();
Logger?.LogWarning($"{ToString()} {request.ErrorMessage}");
return false;
}
if (request.BodyLength + request.HeaderLength > byteBlock.CanReadLength)
{
//body不满足解析开始缓存然后保存对象
return;
return false;
}
//if (request.BodyLength <= 0)
//{
// //如果body长度无法确定直接读取全部
// request.BodyLength = byteBlock.Length;
//}
var headPos = pos + request.HeaderLength;
byteBlock.Position = headPos;
byteBlock.BytesRead = headPos;
var result = request.CheckBody(ref byteBlock);
if (result == FilterResult.Cache)
{
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}-Received incomplete, cached message, current length:{byteBlock.Length} {request?.ErrorMessage}");
Logger?.Trace($"{ToString()}-Received incomplete, cached message, need length:{request.HeaderLength + request.BodyLength} ,current length:{byteBlock.BytesRead + byteBlock.BytesRemaining} {request?.ErrorMessage}");
request.OperCode = -1;
}
else if (result == FilterResult.GoOn)
{
var addLen = request.HeaderLength + request.BodyLength;
byteBlock.Position = pos + (addLen > 0 ? addLen : 1);
Logger?.Trace($"{ToString()}-{request?.ToString()}");
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}-{request?.ToString()}");
request.OperCode = -1;
if ((Owner as IClientChannel)?.WaitHandlePool?.TryGetDataAsync(request.Sign, out var waitDataAsync) == true)
{
waitDataAsync.SetResult(request);
}
}
else if (result == FilterResult.Success)
{
var addLen = request.HeaderLength + request.BodyLength;
byteBlock.Position = pos + (addLen > 0 ? addLen : 1);
await GoReceived(remoteEndPoint, null, request).ConfigureAwait(false);
request1 = request;
return true;
}
return;
}
else
{
byteBlock.Position = pos + 1;
request.OperCode = -1;
return;
return false;
}
return false;
}
catch (Exception ex)
{
Logger?.LogWarning(ex, $"{ToString()} Received parsing error");
byteBlock.Position = byteBlock.Length;//移动游标
return;
return false;
}
}
#endregion
/// <inheritdoc/>
protected override async Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
protected override Task PreviewReceivedAsync(EndPoint remoteEndPoint, ReadOnlyMemory<byte> memory)
{
if (ParseRequestCore(remoteEndPoint, memory, out var request))
{
return GoReceived(remoteEndPoint, null, request);
}
return EasyTask.CompletedTask;
}
/// <inheritdoc/>
protected override Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString(' ') : (memory.Span.ToString(Encoding.UTF8)))}");
//发送
await GoSendAsync(endPoint, memory, cancellationToken).ConfigureAwait(false);
return GoSendAsync(endPoint, memory, cancellationToken);
}
/// <inheritdoc/>
protected override async Task PreviewSendAsync(EndPoint endPoint, IRequestInfo requestInfo, CancellationToken cancellationToken)
protected override Task PreviewSendAsync(EndPoint endPoint, IRequestInfo requestInfo, CancellationToken cancellationToken)
{
if (!(requestInfo is ISendMessage sendMessage))
{
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
}
cancellationToken.ThrowIfCancellationRequested();
var byteBlock = new ValueByteBlock(sendMessage.MaxLength);
try
{
@@ -173,9 +194,9 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
if (IsSingleThread)
{
SetRequest(sendMessage, ref byteBlock);
SetRequest(sendMessage);
}
await GoSendAsync(endPoint, byteBlock.Memory, cancellationToken).ConfigureAwait(false);
return GoSendAsync(endPoint, byteBlock.Memory, cancellationToken);
}
finally
{

View File

@@ -1,46 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// 定义了字节块构建器的接口,用于从内存池中构建和管理字节块。
/// </summary>
public interface IByteBlockWriterBuilder
{
/// <summary>
/// 构建数据时,指示内存池的申请长度。
/// <para>
/// 建议:该值可以尽可能的设置大一些,这样可以避免内存池扩容。
/// </para>
/// </summary>
int MaxLength { get; }
/// <summary>
/// 构建对象到<see cref="ByteBlock"/>
/// </summary>
/// <param name="writer">要构建的字节块对象引用。</param>
void Build<TWriter>(ref TWriter writer) where TWriter : IByteBlockWriter
#if AllowsRefStruct
,allows ref struct
#endif
;
}
/// <summary>
/// 指示<see cref="IRequestInfo"/>应当如何构建
/// </summary>
public interface IRequestInfoByteBlockWriterBuilder : IRequestInfo, IByteBlockWriterBuilder
{
}

View File

@@ -0,0 +1,21 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation
{
public interface IDeviceDataHandleAdapter
{
bool CanSendRequestInfo { get; }
bool IsHexLog { get; set; }
bool IsSingleThread { get; set; }
ILog Logger { get; set; }
}
}

View File

@@ -18,7 +18,7 @@ public interface IResultMessage : IOperResult, IRequestInfo
/// <summary>
/// 数据体长度
/// </summary>
int BodyLength { get; set; }
long BodyLength { get; set; }
/// <summary>
/// 解析的字节信息
@@ -28,7 +28,7 @@ public interface IResultMessage : IOperResult, IRequestInfo
/// <summary>
/// 消息头的指令长度,不固定时返回0
/// </summary>
int HeaderLength { get; }
long HeaderLength { get; }
/// <summary>
/// 等待标识,对于并发协议,必须从协议中例如固定头部获取标识字段
@@ -42,17 +42,17 @@ public interface IResultMessage : IOperResult, IRequestInfo
/// <para>然后返回<see cref="FilterResult.GoOn"/></para>
/// </summary>
/// <returns>是否成功有效</returns>
FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader;
/// <summary>
/// 检查头子节的合法性,并赋值<see cref="BodyLength"/><br />
/// <para>如果返回false意味着放弃本次解析的所有数据包括已经解析完成的Header</para>
/// </summary>
/// <returns>是否成功的结果</returns>
bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader;
/// <summary>
/// 发送前的信息处理,例如存储某些特征信息:站号/功能码等等用于验证后续的返回信息是否合法
/// </summary>
void SendInfo(ISendMessage sendMessage, ref ValueByteBlock byteBlock);
void SendInfo(ISendMessage sendMessage);
}

View File

@@ -13,6 +13,6 @@ namespace ThingsGateway.Foundation;
/// <summary>
/// 发送消息
/// </summary>
public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoByteBlockWriterBuilder
public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoBuilder
{
}

View File

@@ -33,28 +33,28 @@ public class MessageBase : OperResultClass<ReadOnlyMemory<byte>>, IResultMessage
#endregion
/// <inheritdoc/>
public int BodyLength { get; set; }
public long BodyLength { get; set; }
/// <inheritdoc/>
public virtual int HeaderLength { get; set; }
public virtual long HeaderLength { get; set; }
/// <inheritdoc/>
public virtual int Sign { get; set; } = -1;
/// <inheritdoc />
public virtual FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader
public virtual FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader
{
return FilterResult.Success;
}
/// <inheritdoc/>
public virtual bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader
public virtual bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader
{
return true;
}
/// <inheritdoc/>
public virtual void SendInfo(ISendMessage sendMessage, ref TouchSocket.Core.ValueByteBlock byteBlock)
public virtual void SendInfo(ISendMessage sendMessage)
{
}
}

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