Compare commits

...

42 Commits

Author SHA1 Message Date
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
Diego
34000d8d7d !68 10.10.7 2025-08-05 09:22:11 +00:00
2248356998 qq.com
e785f6660c 10.10.5 2025-08-01 21:53:47 +08:00
Diego
831c611797 10.10.4 2025-08-01 17:30:37 +08:00
Diego
453817ef86 添加IAsyncDisposable 2025-08-01 16:36:27 +08:00
2248356998 qq.com
8ce0b981c1 no message 2025-08-01 12:55:01 +08:00
2248356998 qq.com
4e5c51b54c 2025-08-01 12:47:21 +08:00
2248356998 qq.com
3cc9d31f28 修改可用内存策略 2025-08-01 12:43:39 +08:00
2248356998 qq.com
10391f869b 支持相对路径创建sqlite 2025-07-31 23:41:58 +08:00
Diego
fba0723a6d 更新依赖 2025-07-31 18:39:46 +08:00
Diego
2db3f78f0c 添加 控制写操作与读操作的比率 的插件配置属性 2025-07-31 18:18:41 +08:00
Diego
badf61fe01 10.9.99 2025-07-31 16:25:47 +08:00
Diego
d74e0952dc 10.9.98 2025-07-31 15:57:33 +08:00
Diego
fb1699ce80 10.9.97 2025-07-31 14:06:47 +08:00
Diego
44adddbcd4 10.9.97 2025-07-31 13:56:49 +08:00
Diego
0eab889452 10.9.97 2025-07-31 13:56:34 +08:00
Diego
e14d39a459 添加 rpc写入 多写日志 2025-07-31 12:52:40 +08:00
2248356998 qq.com
7575264ede 添加变量初始化标记 2025-07-31 00:40:03 +08:00
2248356998 qq.com
3e1a077b96 添加变量初始化标记 2025-07-31 00:30:53 +08:00
2248356998 qq.com
a921cb8400 refactor: 连接时判断setup 2025-07-30 23:27:35 +08:00
2248356998 qq.com
4de7c31ed7 10.9.92 2025-07-30 22:00:23 +08:00
2248356998 qq.com
08326a2cfd fix: 单属性验证异常 2025-07-30 21:11:35 +08:00
Diego
e045de5acb !67 更新依赖 2025-07-30 10:01:20 +00:00
2248356998 qq.com
d3bef31aa6 10.9.70 2025-07-30 04:32:14 +08:00
2248356998 qq.com
347d4d6e5d 10.9.69 2025-07-25 22:21:34 +08:00
2248356998 qq.com
b006a066e1 10.9.69 2025-07-25 22:14:06 +08:00
Diego
e331bc5d3c 10.9.68 2025-07-25 20:30:48 +08:00
Diego
2e81806231 优化 2025-07-25 20:20:35 +08:00
Diego
37b48cf221 BindField 2025-07-24 20:03:53 +08:00
Diego
5997487434 修改参数 2025-07-24 19:46:30 +08:00
757 changed files with 11373 additions and 11404 deletions

1
.gitignore vendored
View File

@@ -367,3 +367,4 @@ FodyWeavers.xsd
/src/*pro*
/src/*pro*/
/src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json
/src/.idea/

View File

@@ -122,6 +122,7 @@ using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using System.Threading.Tasks;
#pragma warning disable CA1849
#pragma warning disable CA2007
#pragma warning disable CS0162
#pragma warning disable CS8632
@@ -276,6 +277,7 @@ namespace {namespaceName}
#pragma warning restore CS8632
#pragma warning restore CS0162
#pragma warning restore CA2007
#pragma warning restore CA1849
");
var bases = class_symbol.GetTypeHierarchy().Where(t => !SymbolEqualityComparer.Default.Equals(t, class_symbol));
var members = class_symbol.GetMembers() // members of the type itself

View File

@@ -1,5 +1,5 @@
<div align="center"><h1 align="center">ThingsBlazor</a></h1></div>
<div align="center"><h1 align="center">ThingsBlazor</h1></div>
<div align="center"><h3 align="center">权限管理框架</h3></div>

View File

@@ -38,9 +38,9 @@ public sealed class OperDescAttribute : MoAttribute
static OperDescAttribute()
{
// 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中
Task.Factory.StartNew(ProcessQueue, TaskCreationOptions.LongRunning);
AppService = App.RootServices.GetService<IAppService>();
// 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中
Task.Factory.StartNew(ProcessQueueAsync, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
}
public OperDescAttribute(string description, bool isRecordPar = true, object localizerType = null)
@@ -93,7 +93,7 @@ public sealed class OperDescAttribute : MoAttribute
/// <summary>
/// 将日志消息写入数据库中
/// </summary>
private static async Task ProcessQueue()
private static async Task ProcessQueueAsync()
{
var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!;
while (!appLifetime.ApplicationStopping.IsCancellationRequested)

View File

@@ -34,12 +34,16 @@ public class FileController : ControllerBase
return BadRequest("Invalid file name.");
}
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", fileName);
if (!System.IO.File.Exists(filePath))
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))
{
return NotFound();
}
#pragma warning restore CA3003
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

View File

@@ -38,6 +38,7 @@ public class VerificatInfo : PrimaryIdEntity
[AutoGenerateColumn(Filterable = true, Sortable = true)]
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
[IgnoreExcel]
[System.ComponentModel.DataAnnotations.Key]
public override long Id { get; set; }
/// <summary>

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Admin.Application;
public class USheetDatas
{

View File

@@ -30,7 +30,7 @@ public class AdminOAuthHandler<TOptions>(
{
static AdminOAuthHandler()
{
Task.Factory.StartNew(Insertable, TaskCreationOptions.LongRunning);
Task.Factory.StartNew(InsertableAsync, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
}
/// <summary>
@@ -41,7 +41,7 @@ public class AdminOAuthHandler<TOptions>(
/// <summary>
/// 创建访问日志
/// </summary>
private static async Task Insertable()
private static async Task InsertableAsync()
{
var db = DbContext.GetDB<SysOperateLog>();
var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!;

View File

@@ -128,18 +128,19 @@ internal sealed class SysOrgService : BaseService<SysOrg>, ISysOrgService
[OperDesc("DeleteOrg")]
public async Task<bool> DeleteOrgAsync(IEnumerable<long> ids)
{
var sysDeleteOrgList = new List<long>();//需要删除的组织ID集合
var sysOrgList = await GetAllAsync().ConfigureAwait(false);//获取所有组织
foreach (var it in ids)
{
var children = SysOrgService.GetSysOrgChildren(sysOrgList, it);//查找下级组织
sysDeleteOrgList.AddRange(children.Select(it => it.Id).ToList());
sysDeleteOrgList.Add(it);
}
//获取所有ID
if (ids.Any())
if (sysDeleteOrgList.Count != 0)
{
using var db = GetDB();
var sysOrgList = await GetAllAsync().ConfigureAwait(false);//获取所有组织
var sysDeleteOrgList = new List<long>();//需要删除的组织ID集合
foreach (var it in ids)
{
var children = SysOrgService.GetSysOrgChildren(sysOrgList, it);//查找下级组织
sysDeleteOrgList.AddRange(children.Select(it => it.Id).ToList());
sysDeleteOrgList.Add(it);
}
//如果组织下有用户则不能删除
if (await db.Queryable<SysUser>().AnyAsync(it => sysDeleteOrgList.Contains(it.OrgId)).ConfigureAwait(false))
{

View File

@@ -75,7 +75,7 @@ public class SysPositionService : BaseService<SysPosition>, ISysPositionService
}
var dels = (await GetAllAsync().ConfigureAwait(false)).Where(a => ids.Contains(a.Id));
await SysUserService.CheckApiDataScopeAsync(dels.Select(a => a.OrgId).ToList(), dels.Select(a => a.CreateUserId).ToList()).ConfigureAwait(false);
await SysUserService.CheckApiDataScopeAsync(dels.Select(a => a.OrgId), dels.Select(a => a.CreateUserId)).ConfigureAwait(false);
//删除职位
var result = await base.DeleteAsync(ids).ConfigureAwait(false);
if (result)

View File

@@ -29,7 +29,7 @@ public interface ISysResourceService
/// <param name="resourceList">资源列表</param>
/// <param name="parentId">父ID</param>
/// <returns></returns>
IEnumerable<SysResource> ConstructMenuTrees(IEnumerable<SysResource> resourceList, long parentId = 0);
IEnumerable<SysResource> ConstructMenuTrees(List<SysResource> resourceList, long parentId = 0);
/// <summary>
/// 复制资源到其他模块
@@ -44,7 +44,7 @@ public interface ISysResourceService
/// </summary>
/// <param name="ids">id列表</param>
/// <returns></returns>
Task<bool> DeleteResourceAsync(IEnumerable<long> ids);
Task<bool> DeleteResourceAsync(HashSet<long> ids);
/// <summary>
/// 从缓存/数据库读取全部资源列表

View File

@@ -75,10 +75,10 @@ internal sealed class SysResourceService : BaseService<SysResource>, ISysResourc
/// <param name="ids">id列表</param>
/// <returns></returns>
[OperDesc("DeleteResource")]
public async Task<bool> DeleteResourceAsync(IEnumerable<long> ids)
public async Task<bool> DeleteResourceAsync(HashSet<long> ids)
{
//删除
if (ids.Any())
if (ids.Count != 0)
{
//获取所有菜单和按钮
var resourceList = await GetAllAsync().ConfigureAwait(false);
@@ -86,10 +86,11 @@ internal sealed class SysResourceService : BaseService<SysResource>, ISysResourc
var delSysResources = resourceList.Where(it => ids.Contains(it.Id));
//找到要删除的模块
var delModules = resourceList.Where(a => a.Category == ResourceCategoryEnum.Module).Where(it => ids.Contains(it.Id));
if (delModules.Any())
//获取模块下的所有列表
var delHashSet = delModules.Select(a => a.Id).ToHashSet();
if (delHashSet.Count != 0)
{
//获取模块下的所有列表
var delHashSet = delModules.Select(a => a.Id).ToHashSet();
var delModuleResources = resourceList.Where(it => delHashSet.Contains(it.Module));
delSysResources = delSysResources.Concat(delModuleResources).ToHashSet();
}
@@ -345,17 +346,14 @@ internal sealed class SysResourceService : BaseService<SysResource>, ISysResourc
}
/// <inheritdoc/>
public IEnumerable<SysResource> ConstructMenuTrees(IEnumerable<SysResource> resourceList, long parentId = 0)
public IEnumerable<SysResource> ConstructMenuTrees(List<SysResource> resourceList, long parentId = 0)
{
//找下级资源ID列表
var resources = resourceList.Where(it => it.ParentId == parentId).OrderBy(it => it.SortCode);
if (resources.Any())//如果数量大于0
foreach (var item in resources)//遍历资源
{
foreach (var item in resources)//遍历资源
{
var children = ConstructMenuTrees(resourceList, item.Id).ToList();//添加子节点
item.Children = children.Count > 0 ? children : null;
}
var children = ConstructMenuTrees(resourceList, item.Id).ToList();//添加子节点
item.Children = children.Count > 0 ? children : null;
}
return resources;
}

View File

@@ -30,7 +30,7 @@ public interface ISysRoleService
/// 删除角色
/// </summary>
/// <param name="ids">id列表</param>
Task<bool> DeleteRoleAsync(IEnumerable<long> ids);
Task<bool> DeleteRoleAsync(HashSet<long> ids);
/// <summary>
/// 从缓存/数据库获取全部角色信息
@@ -43,7 +43,7 @@ public interface ISysRoleService
/// </summary>
/// <param name="input">角色id列表</param>
/// <returns>角色列表</returns>
Task<IEnumerable<SysRole>> GetRoleListByIdListAsync(IEnumerable<long> input);
Task<IEnumerable<SysRole>> GetRoleListByIdListAsync(HashSet<long> input);
/// <summary>
/// 根据用户id获取角色列表

View File

@@ -61,42 +61,46 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService
var topOrgList = sysOrgList.Where(it => it.ParentId == 0);//获取顶级机构
var globalRole = sysRoles.Where(it => it.Category == RoleCategoryEnum.Global);//获取全局角色
if (globalRole.Any())
var children = globalRole.Select(it => new RoleTreeOutput
{
result.Add(new RoleTreeOutput()
{
Id = CommonUtils.GetSingleId(),
Name = Localizer["Global"],
Children = globalRole.Select(it => new RoleTreeOutput
{
Id = it.Id,
Name = it.Name,
IsRole = true
}).ToList()
});//添加全局角色
}
Id = it.Id,
Name = it.Name,
IsRole = true
}).ToList();
result.Add(new RoleTreeOutput()
{
Id = CommonUtils.GetSingleId(),
Name = Localizer["Global"],
Children = children
});//添加全局角色
//遍历顶级机构
foreach (var org in topOrgList)
{
var childIds = await _sysOrgService.GetOrgChildIdsAsync(org.Id, true, sysOrgList).ConfigureAwait(false);//获取机构下的所有子级ID
var childRoles = sysRoles.Where(it => it.OrgId != 0 && childIds.Contains(it.OrgId));//获取机构下的所有角色
if (childRoles.Any())
List<RoleTreeOutput> childrenRoleTreeOutputs = new();
foreach (var it in childRoles)
{
childrenRoleTreeOutputs.Add(new RoleTreeOutput()
{
Id = it.Id,
Name = it.Name,
IsRole = true
});
}
if (childrenRoleTreeOutputs.Count > 0)
{
var roleTreeOutput = new RoleTreeOutput
{
Id = org.Id,
Name = org.Name,
IsRole = false
IsRole = false,
Children = childrenRoleTreeOutputs
};//实例化角色树
foreach (var it in childRoles)
{
roleTreeOutput.Children.Add(new RoleTreeOutput()
{
Id = it.Id,
Name = it.Name,
IsRole = true
});
}
result.Add(roleTreeOutput);
}
}
@@ -147,7 +151,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService
/// </summary>
/// <param name="input">角色id列表</param>
/// <returns>角色列表</returns>
public async Task<IEnumerable<SysRole>> GetRoleListByIdListAsync(IEnumerable<long> input)
public async Task<IEnumerable<SysRole>> GetRoleListByIdListAsync(HashSet<long> input)
{
var roles = await GetAllAsync().ConfigureAwait(false);
var roleList = roles.Where(it => input.Contains(it.Id));
@@ -162,7 +166,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService
/// </summary>
/// <param name="ids">id列表</param>
[OperDesc("DeleteRole")]
public async Task<bool> DeleteRoleAsync(IEnumerable<long> ids)
public async Task<bool> DeleteRoleAsync(HashSet<long> ids)
{
var sysRoles = await GetAllAsync().ConfigureAwait(false);//获取所有角色
var hasSuperAdmin = sysRoles.Any(it => it.Id == RoleConst.SuperAdminRoleId && ids.Contains(it.Id));//判断是否有超级管理员
@@ -170,10 +174,10 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService
throw Oops.Bah(Localizer["CanotDeleteAdmin"]);
var dels = (await GetAllAsync().ConfigureAwait(false)).Where(a => ids.Contains(a.Id));
await SysUserService.CheckApiDataScopeAsync(dels.Select(a => a.OrgId).ToList(), dels.Select(a => a.CreateUserId).ToList()).ConfigureAwait(false);
await SysUserService.CheckApiDataScopeAsync(dels.Select(a => a.OrgId), dels.Select(a => a.CreateUserId)).ConfigureAwait(false);
//数据库是string所以这里转下
var targetIds = ids.Select(it => it.ToString());
var targetIds = ids.Select(it => it.ToString()).ToList();
//定义删除的关系
var delRelations = new List<RelationCategoryEnum> {
RelationCategoryEnum.RoleHasResource,
@@ -184,7 +188,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService
//事务
var result = await db.UseTranAsync(async () =>
{
await db.Deleteable<SysRole>().In(ids.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false);//删除
await db.Deleteable<SysRole>().In(ids).ExecuteCommandHasChangeAsync().ConfigureAwait(false);//删除
//删除关系表角色与资源关系,角色与权限关系
await db.Deleteable<SysRelation>(it => ids.Contains(it.ObjectId) && delRelations.Contains(it.Category)).ExecuteCommandAsync().ConfigureAwait(false);
//删除关系表角色与用户关系
@@ -317,7 +321,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService
#region .
var defaultDataScope = sysRole.DefaultDataScope;//获取默认数据范围
if (menusList.Any())
if (relationRoles.Count != 0)
{
//获取权限授权树
var permissions = App.GetService<IApiPermissionService>().PermissionTreeSelector(menusList.Select(it => it.Href));

View File

@@ -18,6 +18,7 @@ public class SessionOutput : PrimaryIdEntity
/// <summary>
/// 主键Id
/// </summary>
[System.ComponentModel.DataAnnotations.Key]
public override long Id { get; set; }
/// <summary>

View File

@@ -58,7 +58,7 @@ public interface ISysUserService
/// </summary>
/// <param name="ids">用户ID列表。</param>
/// <returns>是否删除成功。</returns>
Task<bool> DeleteUserAsync(IEnumerable<long> ids);
Task<bool> DeleteUserAsync(HashSet<long> ids);
/// <summary>
/// 从缓存中删除用户信息。

View File

@@ -587,7 +587,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
#region .
//获取菜单信息
if (menusList.Any())
if (relationUsers.Count != 0)
{
//获取权限授权树
var permissions = App.GetService<IApiPermissionService>().PermissionTreeSelector(menusList.Select(it => it.Href));
@@ -642,7 +642,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
/// <inheritdoc/>
[OperDesc("DeleteUser")]
public async Task<bool> DeleteUserAsync(IEnumerable<long> ids)
public async Task<bool> DeleteUserAsync(HashSet<long> ids)
{
using var db = GetDB();
var containsSuperAdmin = await db.Queryable<SysUser>().Where(it => it.Id == RoleConst.SuperAdminId && ids.Contains(it.Id)).AnyAsync().ConfigureAwait(false);//判断是否有超管
@@ -672,7 +672,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
.ExecuteCommandAsync().ConfigureAwait(false);
//删除用户
await db.Deleteable<SysUser>().In(ids.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false);//删除
await db.Deleteable<SysUser>().In(ids).ExecuteCommandHasChangeAsync().ConfigureAwait(false);//删除
//删除关系表用户与资源关系,用户与权限关系,用户与角色关系
await db.Deleteable<SysRelation>(it => ids.Contains(it.ObjectId) && delRelations.Contains(it.Category)).ExecuteCommandAsync().ConfigureAwait(false);
@@ -718,16 +718,16 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
public void DeleteUserFromCache(IEnumerable<long> ids)
{
var userIds = ids.Select(it => it.ToString()).ToArray();//id转string列表
var sysUsers = App.CacheService.HashGet<SysUser>(CacheConst.Cache_SysUser, userIds).Where(it => it != null);//获取用户列表
if (sysUsers.Any() == true)
var sysUsers = App.CacheService.HashGet<SysUser>(CacheConst.Cache_SysUser, userIds);//获取用户列表
if (sysUsers.Count != 0)
{
var accounts = sysUsers.Where(it => it != null).Select(it => it.Account).ToArray();//账号集合
var phones = sysUsers.Select(it => it.Phone);//手机号集合
var phones = sysUsers.Select(it => it?.Phone);//手机号集合
if (sysUsers.Any(it => it.TenantId != null))//如果有租户id不是空的表示是多租户模式
if (sysUsers.Any(it => it?.TenantId != null))//如果有租户id不是空的表示是多租户模式
{
var userAccountKey = CacheConst.Cache_SysUserAccount;
var tenantIds = sysUsers.Where(it => it.TenantId != null).Select(it => it.TenantId.Value).Distinct().ToArray();//租户id列表
var tenantIds = sysUsers.Where(it => it?.TenantId != null).Select(it => it.TenantId.Value).Distinct().ToArray();//租户id列表
foreach (var tenantId in tenantIds)
{
userAccountKey = $"{userAccountKey}:{tenantId}";

View File

@@ -145,7 +145,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi
public void Delete(long id)
{
using var db = GetDB();
db.Deleteable<VerificatInfo>(id).ExecuteCommand();
db.Deleteable<VerificatInfo>(a => a.Id == id).ExecuteCommand();
VerificatInfoService.RemoveCache(id);
}

View File

@@ -0,0 +1,57 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
public static class USheetDataHelpers
{
public static USheetDatas GetUSheetDatas(Dictionary<string, object> data)
{
var uSheetDatas = new USheetDatas();
foreach (var a in data)
{
var value = (a.Value as IEnumerable<Dictionary<string, object>>).ToList();
var uSheetData = new USheetData();
uSheetData.id = a.Key;
uSheetData.name = a.Key;
for (int row1 = 0; row1 < value.Count; row1++)
{
if (row1 == 0)
{
Dictionary<int, USheetCelldata> usheetColldata = new();
int col = 0;
foreach (var colData in value[row1])
{
usheetColldata.Add(col, new USheetCelldata() { v = colData.Key });
col++;
}
uSheetData.cellData.Add(row1, usheetColldata);
}
{
Dictionary<int, USheetCelldata> usheetColldata = new();
int col = 0;
foreach (var colData in value[row1])
{
usheetColldata.Add(col, new USheetCelldata() { v = colData.Value });
col++;
}
uSheetData.cellData.Add(row1 + 1, usheetColldata);
}
}
uSheetData.rowCount = uSheetData.cellData.Count + 100;
uSheetData.columnCount = uSheetData.cellData.FirstOrDefault().Value?.Count ?? 0;
uSheetDatas.sheets.Add(a.Key, uSheetData);
}
return uSheetDatas;
}
}

View File

@@ -31,12 +31,13 @@ public partial class ChoiceTable<TItem> where TItem : class, new()
public async Task OnAddAsync(IEnumerable<TItem> selectorOutputs)
{
if (MaxCount > 0 && selectorOutputs.Count() + SelectedRows.Count > MaxCount)
var data = selectorOutputs is IReadOnlyCollection<TItem> list ? list : selectorOutputs.ToList();
if (MaxCount > 0 && data.Count + SelectedRows.Count > MaxCount)
{
await ToastService.Warning(AdminLocalizer["MaxCount"]);
return;
}
foreach (var item in selectorOutputs)
foreach (var item in data)
{
SelectedRows.Add(item);
await table2.QueryAsync();

View File

@@ -1,7 +1,6 @@
@namespace ThingsGateway.Gateway.Razor
@namespace ThingsGateway.Admin.Razor
@using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Gateway.Application
<div class="h-600px">
<UniverSheet @ref="_sheetExcel" OnReadyAsync="OnReadyAsync"></UniverSheet>

View File

@@ -8,9 +8,10 @@
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Admin.Application;
using ThingsGateway.NewLife.Json.Extension;
namespace ThingsGateway.Gateway.Razor;
namespace ThingsGateway.Admin.Razor;
public partial class USheet
{

View File

@@ -93,7 +93,7 @@ public partial class SysResourcePage
{
try
{
var result = await SysResourceService.DeleteResourceAsync(sysResources.Select(a => a.Id));
var result = await SysResourceService.DeleteResourceAsync(sysResources.Select(a => a.Id).ToHashSet());
if (ReloadUser != null)
{
await ReloadUser();

View File

@@ -50,7 +50,7 @@ public partial class SysRolePage
{
try
{
return await SysRoleService.DeleteRoleAsync(sysRoles.Select(a => a.Id));
return await SysRoleService.DeleteRoleAsync(sysRoles.Select(a => a.Id).ToHashSet());
}
catch (Exception ex)
{

View File

@@ -45,7 +45,7 @@ public partial class SysUserPage
{
try
{
return await SysUserService.DeleteUserAsync(sysUsers.Select(a => a.Id));
return await SysUserService.DeleteUserAsync(sysUsers.Select(a => a.Id).ToHashSet());
}
catch (Exception ex)
{

View File

@@ -6,6 +6,7 @@
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net8.0'">

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

@@ -45,11 +45,11 @@ public class Startup : AppStartup
options.ServicesStopConcurrently = true;
});
//// 事件总线
//services.AddEventBus(options =>
//{
// 事件总线
services.AddEventBus(options =>
{
//});
});
// 任务调度
services.AddSchedule(options => options.AddPersistence<JobPersistence>());

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Common;
public class SmartTriggerScheduler
{

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Common;
public sealed class StringOrdinalIgnoreCaseEqualityComparer : EqualityComparer<string>
{

View File

@@ -443,10 +443,7 @@ public static class ObjectExtensions
where TAttribute : Attribute
{
// 空检查
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
ArgumentNullException.ThrowIfNull(type);
// 检查特性并获取特性对象
return type.IsDefined(typeof(TAttribute), inherit)

View File

@@ -27,7 +27,11 @@ public class WebsiteOptions : IConfigurableOptions
/// </summary>
public bool Demo { get; set; }
public bool WebPageEnable { get; set; } = true;
public int MaxBlazorConnections { get; set; } = 5;
public bool BlazorConnectionLimitEnable { get; set; } = false;
/// <summary>
/// 是否显示关于页面
/// </summary>

View File

@@ -13,7 +13,7 @@
<ItemGroup>
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
<PackageReference Include="BootstrapBlazor" Version="9.8.2" />
<PackageReference Include="BootstrapBlazor" Version="9.9.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -23,6 +23,7 @@ public abstract class PrimaryIdEntity : IPrimaryIdEntity
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
[IgnoreExcel]
[AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)]
[System.ComponentModel.DataAnnotations.Key]
public virtual long Id { get; set; }
}

View File

@@ -123,6 +123,15 @@ 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;
}

View File

@@ -43,12 +43,12 @@ public class SugarAopService : ISugarAopService
}
if (sql.StartsWith("INSERT"))
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.ForegroundColor = ConsoleColor.Blue;
DbContext.WriteLog($"添加{config.ConfigId}库操作");
}
if (sql.StartsWith("DELETE"))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.ForegroundColor = ConsoleColor.Blue;
DbContext.WriteLog($"删除{config.ConfigId}库操作");
}
DbContext.WriteLogWithSql(UtilMethods.GetNativeSql(sql, pars));
@@ -62,7 +62,7 @@ public class SugarAopService : ISugarAopService
if (ex.Parameters == null) return;
Console.ForegroundColor = ConsoleColor.Red;
DbContext.WriteLog($"{config.ConfigId}库操作异常");
DbContext.WriteErrorLogWithSql(UtilMethods.GetNativeSql(ex.Sql, (IReadOnlyList<SugarParameter>)ex.Parameters));
DbContext.WriteErrorLogWithSql(UtilMethods.GetNativeSql(ex.Sql, ex.Parameters));
NewLife.Log.XTrace.WriteException(ex);
Console.ForegroundColor = ConsoleColor.White;
};
@@ -110,10 +110,6 @@ public class SugarAopService : ISugarAopService
}
};
//查询数据转换
db.Aop.DataExecuted = (value, entity) =>
{
};
db.Aop.OnLogExecuted = (sql, pars) =>
{

View File

@@ -46,7 +46,7 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new(
public async Task<bool> DeleteAsync(IEnumerable<T> models)
{
using var db = GetDB();
return await db.Deleteable<T>().In(models.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false);
return await db.Deleteable<T>(models.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false);
}
/// <inheritdoc/>
@@ -140,18 +140,22 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new(
return (await db.UpdateableT(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
}
}
/// <inheritdoc/>
public virtual async Task<bool> SaveAsync(List<T> model, ItemChangedType changedType)
{
return (await SaveReturnCountAsync(model, changedType).ConfigureAwait(false)) > 0;
}
/// <inheritdoc/>
public async Task<int> SaveReturnCountAsync(List<T> model, ItemChangedType changedType)
{
using var db = GetDB();
if (changedType == ItemChangedType.Add)
{
return (await db.Insertable(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
return (await db.Insertable(model).ExecuteCommandAsync().ConfigureAwait(false));
}
else
{
return (await db.Updateable(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
return (await db.Updateable(model).ExecuteCommandAsync().ConfigureAwait(false));
}
}
/// <summary>

View File

@@ -42,7 +42,6 @@ public static class CodeFirstUtils
var seedDataTypes = App.EffectiveTypes
.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass
&& u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>))) && u.Assembly.FullName == assemblyName);
if (!seedDataTypes.Any()) return;
foreach (var seedType in seedDataTypes)//遍历种子类
{
//使用与指定参数匹配程度最高的构造函数来创建指定类型的实例。
@@ -62,10 +61,12 @@ public static class CodeFirstUtils
// seedDataTable.TableName = db.EntityMaintenance.GetEntityInfo(entityType).DbTableName;//获取表名
var ignoreAdd = seedDataMethod!.GetCustomAttribute<IgnoreSeedDataAddAttribute>();//读取忽略插入特性
var ignoreUpdate = seedDataMethod!.GetCustomAttribute<IgnoreSeedDataUpdateAttribute>();//读取忽略更新特性
var seedDataList = seedData.ToList();
if (entityInfo.Columns.Any(u => u.IsPrimarykey))//判断种子数据是否有主键
{
// 按主键进行批量增加和更新
var storage = db.StorageableByObject(seedData.ToList()).ToStorage();
var storage = db.StorageableByObject(seedDataList).ToStorage();
if (ignoreAdd == null)
storage.AsInsertable.ExecuteCommand();//执行插入
if (ignoreUpdate == null && config.IsUpdateSeedData) storage.AsUpdateable.ExecuteCommand();//只有没有忽略更新的特性才执行更新
@@ -75,7 +76,7 @@ public static class CodeFirstUtils
//全量插入
// 无主键则只进行插入
if (!db.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any() && ignoreAdd == null)
db.InsertableByObject(seedData.ToList()).ExecuteCommand();
db.InsertableByObject(seedDataList).ExecuteCommand();
}
}
}
@@ -89,7 +90,6 @@ public static class CodeFirstUtils
// 获取所有实体表-初始化表结构
var entityTypes = App.EffectiveTypes.Where(u =>
!u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false) && u.Assembly.FullName == assemblyName);
if (!entityTypes.Any()) return;//没有就退出
foreach (var entityType in entityTypes)
{
var tenantAtt = entityType.GetCustomAttribute<TenantAttribute>();//获取Sqlsugar多库特性

View File

@@ -136,7 +136,7 @@ public static class DbContext
await db.Fastest<TITEM>().PageSize(size).BulkCopyAsync(datas).ConfigureAwait(false);
break;
default:
await db.Insertable(datas is IReadOnlyList<TITEM> values ? values : datas.ToList()).PageSize(size).ExecuteCommandAsync().ConfigureAwait(false);
await db.Insertable(datas is IReadOnlyCollection<TITEM> values ? values : datas.ToList()).PageSize(size).ExecuteCommandAsync().ConfigureAwait(false);
break;
}
}
@@ -155,7 +155,7 @@ public static class DbContext
await db.Fastest<TITEM>().PageSize(size).BulkUpdateAsync(datas).ConfigureAwait(false);
break;
default:
await db.Updateable(datas is IReadOnlyList<TITEM> values ? values : datas.ToList()).PageSize(size).ExecuteCommandAsync().ConfigureAwait(false);
await db.Updateable(datas is IReadOnlyCollection<TITEM> values ? values : datas.ToList()).PageSize(size).ExecuteCommandAsync().ConfigureAwait(false);
break;
}
}

View File

@@ -27,18 +27,27 @@ using System.Security.Claims;
using ThingsGateway.ConfigurableOptions;
using ThingsGateway.NewLife.Caching;
using ThingsGateway.NewLife.Collections;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Log;
using ThingsGateway.Reflection;
using ThingsGateway.Templates;
namespace ThingsGateway;
public static class WebEnableVariable
{
public static bool WebEnable => Environment.GetEnvironmentVariable(nameof(WebEnable)).ToBoolean(true);
}
/// <summary>
/// 全局应用类
/// </summary>
[SuppressSniffer]
public static class App
{
/// <summary>
/// 私有设置,避免重复解析
/// </summary>
@@ -157,7 +166,7 @@ public static class App
var httpContextAccessor = RootServices?.GetService<IHttpContextAccessor>();
try
{
return httpContextAccessor.HttpContext;
return httpContextAccessor?.HttpContext;
}
catch
{

View File

@@ -213,12 +213,18 @@ public static class AppServiceCollectionExtensions
// 缓存
if (cacheOptions.CacheType == CacheType.Memory)
{
services.AddSingleton<ICache, MemoryCache>(a => new()
services.AddSingleton<ICache>(a =>
{
Capacity = cacheOptions.MemoryCacheOptions.Capacity,
Expire = cacheOptions.MemoryCacheOptions.Expire,
Period = cacheOptions.MemoryCacheOptions.Period
});
Cache.Default = new MemoryCache()
{
Capacity = cacheOptions.MemoryCacheOptions.Capacity,
Expire = cacheOptions.MemoryCacheOptions.Expire,
Period = cacheOptions.MemoryCacheOptions.Period
};
return Cache.Default;
}
);
}
else if (cacheOptions.CacheType == CacheType.Redis)
{
@@ -278,7 +284,6 @@ public static class AppServiceCollectionExtensions
&& u.GetParameters().Length > 0
&& u.GetParameters().First().ParameterType == typeof(IServiceCollection));
if (!serviceMethods.Any()) continue;
// 自动安装属性调用
foreach (var method in serviceMethods)

View File

@@ -18,6 +18,8 @@ using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.RegularExpressions;
using ThingsGateway.NewLife;
namespace ThingsGateway.Extensions;
/// <summary>
@@ -187,7 +189,7 @@ public static class ObjectExtensions
{
if (current == last) func(arg);
task.Dispose();
});
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);
};
}
@@ -207,8 +209,8 @@ public static class ObjectExtensions
Task.Delay(milliseconds).ContinueWith(task =>
{
if (current == last) func();
task.Dispose();
});
task.TryDispose();
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);
};
}

View File

@@ -85,11 +85,14 @@ internal static class InternalApp
// 存储根服务(解决 Web 主机还未启动时在 HostedService 中使用 App.GetService 问题
services.AddHostedService<GenericHostLifetimeEventsHostedService>();
// 注册 Startup 过滤器
services.AddTransient<IStartupFilter, StartupFilter>();
if (WebEnableVariable.WebEnable == true)
{
// 注册 Startup 过滤器
services.AddTransient<IStartupFilter, StartupFilter>();
// 注册 HttpContextAccessor 服务
services.AddHttpContextAccessor();
// 注册 HttpContextAccessor 服务
services.AddHttpContextAccessor();
}
// 初始化应用服务
services.AddApp();

View File

@@ -20,6 +20,7 @@ namespace ThingsGateway;
/// </summary>
public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions>
{
/// <summary>
/// 是否启用规范化文档
/// </summary>

View File

@@ -0,0 +1,341 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ThingsGateway;
namespace System;
/// <summary>
/// <see cref="WebApplication"/> 方式配置选项
/// </summary>
[SuppressSniffer]
public sealed class MiniRunOptions : IRunOptions
{
/// <summary>
/// 内部构造函数
/// </summary>
internal MiniRunOptions()
{
}
/// <summary>
/// 默认配置
/// </summary>
public static MiniRunOptions Default { get; } = new MiniRunOptions();
/// <summary>
/// 默认配置(带启动参数)
/// </summary>
public static MiniRunOptions Main(string[] args)
{
return Default.WithArgs(args);
}
/// <summary>
/// 默认配置(静默启动)
/// </summary>
public static MiniRunOptions DefaultSilence { get; } = new MiniRunOptions().Silence();
/// <summary>
/// 默认配置(静默启动 + 启动参数)
/// </summary>
public static MiniRunOptions MainSilence(string[] args)
{
return DefaultSilence.WithArgs(args);
}
/// <summary>
/// 配置 <see cref="WebApplicationOptions"/>
/// </summary>
/// <param name="options"></param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions ConfigureOptions(WebApplicationOptions options)
{
Options = options;
return this;
}
/// <summary>
/// 配置 <see cref="IWebHostBuilder"/>
/// </summary>
/// <param name="configureAction"></param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions ConfigureBuilder(Action<IWebHostBuilder> configureAction)
{
ActionBuilder = configureAction;
return this;
}
/// <summary>
/// 配置 <see cref="IHostBuilder"/>
/// </summary>
/// <param name="configureAction"></param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions ConfigureFirstActionBuilder(Action<IHostBuilder> configureAction)
{
FirstActionBuilder = configureAction;
return this;
}
/// <summary>
/// 配置 <see cref="IServiceCollection"/>
/// </summary>
/// <param name="configureAction"></param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions ConfigureServices(Action<IServiceCollection> configureAction)
{
ActionServices = configureAction;
return this;
}
/// <summary>
/// 配置 <see cref="InjectOptions"/>
/// </summary>
/// <param name="configureAction"></param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions ConfigureInject(Action<IWebHostBuilder, InjectOptions> configureAction)
{
ActionInject = configureAction;
return this;
}
/// <summary>
/// 配置 <see cref="WebApplication"/>
/// </summary>
/// <param name="configureAction">配置委托</param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions Configure(Action<IHost> configureAction)
{
ActionConfigure = configureAction;
return this;
}
/// <summary>
/// 配置 <see cref="ConfigurationManager"/>
/// </summary>
/// <param name="configureAction">配置委托</param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions ConfigureConfiguration(Action<IHostEnvironment, IConfiguration> configureAction)
{
ActionConfigurationManager = configureAction;
return this;
}
/// <summary>
/// 添加应用服务组件
/// </summary>
/// <typeparam name="TComponent">组件类型</typeparam>
/// <returns></returns>
public MiniRunOptions AddComponent<TComponent>()
where TComponent : class, IServiceComponent, new()
{
ServiceComponents.Add(typeof(TComponent), null);
return this;
}
/// <summary>
/// 添加应用服务组件
/// </summary>
/// <typeparam name="TComponent">组件类型</typeparam>
/// <typeparam name="TComponentOptions"></typeparam>
/// <param name="options">组件参数</param>
/// <returns></returns>
public MiniRunOptions AddComponent<TComponent, TComponentOptions>(TComponentOptions options)
where TComponent : class, IServiceComponent, new()
{
ServiceComponents.Add(typeof(TComponent), options);
return this;
}
/// <summary>
/// 添加应用服务组件
/// </summary>
/// <param name="componentType">组件类型</param>
/// <param name="options">组件参数</param>
/// <returns></returns>
public MiniRunOptions AddComponent(Type componentType, object options)
{
ServiceComponents.Add(componentType, options);
return this;
}
/// <summary>
/// 添加应用中间件组件
/// </summary>
/// <typeparam name="TComponent">组件类型</typeparam>
/// <returns></returns>
public MiniRunOptions UseComponent<TComponent>()
where TComponent : class, IApplicationComponent, new()
{
ApplicationComponents.Add(typeof(TComponent), null);
return this;
}
/// <summary>
/// 添加应用中间件组件
/// </summary>
/// <typeparam name="TComponent">组件类型</typeparam>
/// <typeparam name="TComponentOptions"></typeparam>
/// <param name="options">组件参数</param>
/// <returns></returns>
public MiniRunOptions UseComponent<TComponent, TComponentOptions>(TComponentOptions options)
where TComponent : class, IApplicationComponent, new()
{
ApplicationComponents.Add(typeof(TComponent), options);
return this;
}
/// <summary>
/// 添加应用中间件组件
/// </summary>
/// <param name="componentType">组件类型</param>
/// <param name="options">组件参数</param>
/// <returns></returns>
public MiniRunOptions UseComponent(Type componentType, object options)
{
ApplicationComponents.Add(componentType, options);
return this;
}
/// <summary>
/// 添加 IWebHostBuilder 组件
/// </summary>
/// <typeparam name="TComponent">组件类型</typeparam>
/// <returns></returns>
public MiniRunOptions AddWebComponent<TComponent>()
where TComponent : class, IWebComponent, new()
{
WebComponents.Add(typeof(TComponent), null);
return this;
}
/// <summary>
/// 添加 IWebHostBuilder 组件
/// </summary>
/// <typeparam name="TComponent">组件类型</typeparam>
/// <typeparam name="TComponentOptions"></typeparam>
/// <param name="options">组件参数</param>
/// <returns></returns>
public MiniRunOptions AddWebComponent<TComponent, TComponentOptions>(TComponentOptions options)
where TComponent : class, IWebComponent, new()
{
WebComponents.Add(typeof(TComponent), options);
return this;
}
/// <summary>
/// 添加 IWebHostBuilder 组件
/// </summary>
/// <param name="componentType">组件类型</param>
/// <param name="options">组件参数</param>
/// <returns></returns>
public MiniRunOptions AddWebComponent(Type componentType, object options)
{
WebComponents.Add(componentType, options);
return this;
}
/// <summary>
/// 标识主机静默启动
/// </summary>
/// <remarks>不阻塞程序运行</remarks>
/// <param name="silence">静默启动</param>
/// <param name="logging">静默启动日志状态,默认 false</param>
/// <returns></returns>
public MiniRunOptions Silence(bool silence = true, bool logging = false)
{
IsSilence = silence;
SilenceLogging = logging;
return this;
}
/// <summary>
/// 设置进程启动参数
/// </summary>
/// <param name="args">启动参数</param>
/// <returns></returns>
public MiniRunOptions WithArgs(string[] args)
{
Args = args;
return this;
}
/// <summary>
/// <see cref="WebApplicationOptions"/>
/// </summary>
internal WebApplicationOptions Options { get; set; }
/// <summary>
/// 自定义 <see cref="IServiceCollection"/> 委托
/// </summary>
internal Action<IServiceCollection> ActionServices { get; set; }
/// <summary>
/// 自定义 <see cref="IWebHostBuilder"/> 委托
/// </summary>
internal Action<IHostBuilder> FirstActionBuilder { get; set; }
/// <summary>
/// 自定义 <see cref="IWebHostBuilder"/> 委托
/// </summary>
internal Action<IWebHostBuilder> ActionBuilder { get; set; }
/// <summary>
/// 自定义 <see cref="InjectOptions"/> 委托
/// </summary>
internal Action<IWebHostBuilder, InjectOptions> ActionInject { get; set; }
/// <summary>
/// 自定义 <see cref="IHost"/> 委托
/// </summary>
internal Action<IHost> ActionConfigure { get; set; }
/// <summary>
/// 自定义 <see cref="IConfiguration"/> 委托
/// </summary>
internal Action<IHostEnvironment, IConfiguration> ActionConfigurationManager { get; set; }
/// <summary>
/// 应用服务组件
/// </summary>
internal Dictionary<Type, object> ServiceComponents { get; set; } = new();
/// <summary>
/// IWebHostBuilder 组件
/// </summary>
internal Dictionary<Type, object> WebComponents { get; set; } = new();
/// <summary>
/// 应用中间件组件
/// </summary>
internal Dictionary<Type, object> ApplicationComponents { get; set; } = new();
/// <summary>
/// 静默启动
/// </summary>
/// <remarks>不阻塞程序运行</remarks>
internal bool IsSilence { get; private set; }
/// <summary>
/// 静默启动日志状态
/// </summary>
internal bool SilenceLogging { get; set; }
/// <summary>
/// 命令行参数
/// </summary>
internal string[] Args { get; set; }
}

View File

@@ -602,6 +602,33 @@ public static class Serve
return app;
}
/// <summary>
/// 启动 WebApplication 主机
/// </summary>
/// <remarks>未包含 Web 基础功能,需手动注册服务/中间件</remarks>
/// <param name="options">配置选项</param>
/// <param name="urls">默认 5000/5001 端口</param>
/// <param name="cancellationToken"></param>
/// <returns><see cref="IHost"/></returns>
public static async Task<IHost> RunAsync(MiniRunOptions options, string urls = default, CancellationToken cancellationToken = default)
{
// 构建 WebApplication 对象
BuildMiniApplication(options, urls, out var app);
// 是否静默启动
if (!options.IsSilence)
{
// 配置启动地址和端口
await app.RunAsync(cancellationToken).ConfigureAwait(false);
}
else
{
await app.StartAsync(cancellationToken).ConfigureAwait(false);
}
return app;
}
/// <summary>
/// 构建 WebApplication 对象
/// </summary>
@@ -616,8 +643,8 @@ public static class Serve
// 初始化 WebApplicationBuilder
var builder = (options.Options == null
? WebApplication.CreateBuilder(args)
: WebApplication.CreateBuilder(options.Options));
? WebApplication.CreateBuilder(args)
: WebApplication.CreateBuilder(options.Options));
// 调用自定义配置服务
options?.FirstActionBuilder?.Invoke(builder);
@@ -799,6 +826,132 @@ public static class Serve
App.AppStartups.Clear();
}
/// <summary>
/// 构建 IHost 对象
/// </summary>
/// <param name="options">配置选项</param>
/// <param name="urls">默认 5000/5001 端口</param>
/// <param name="app"><see cref="IHost"/></param>
public static void BuildMiniApplication(MiniRunOptions options, string urls, out IHost app)
{
// 获取命令行参数
var args = options.Args ?? Environment.GetCommandLineArgs().Skip(1).ToArray();
var builder = Host.CreateDefaultBuilder(args);
// 静默启动排除指定日志类名
if (options.IsSilence && !options.SilenceLogging)
{
builder = builder.ConfigureLogging(logging =>
{
logging.AddFilter((provider, category, logLevel) => !SilenceExcludesOfLogCategoryName.Any(u => category.StartsWith(u)));
});
}
// 配置 Web 主机
builder = builder.ConfigureWebHost(webHostBuilder =>
{
// 调用自定义配置服务
options?.FirstActionBuilder?.Invoke(builder);
// 注册 WebApplicationBuilder 组件
if (options.WebComponents.Count > 0)
{
foreach (var (componentType, opt) in options.WebComponents)
{
webHostBuilder.AddWebComponent(componentType, opt);
}
}
webHostBuilder.Configure((WebHostBuilderContext app, IApplicationBuilder applicationBuilder) =>
{
// 添加自定义配置
options.ActionConfigurationManager?.Invoke(app.HostingEnvironment, app.Configuration);
});
// 初始化框架
webHostBuilder.Inject(options.ActionInject);
// 配置服务
if (options.ServiceComponents.Count > 0)
{
webHostBuilder = webHostBuilder.ConfigureServices(services =>
{
// 注册应用服务组件
foreach (var (componentType, opt) in options.ServiceComponents)
{
services.AddComponent(componentType, opt);
}
});
}
// 配置启动地址和端口
var startUrls = !string.IsNullOrWhiteSpace(urls) ? urls : webHostBuilder.GetSetting(nameof(urls));
// 自定义启动端口
if (!string.IsNullOrWhiteSpace(startUrls))
{
webHostBuilder = webHostBuilder.UseUrls(startUrls);
}
// 调用自定义配置
options?.ActionBuilder?.Invoke(webHostBuilder);
// 配置中间件
if (options.ApplicationComponents.Count > 0)
{
webHostBuilder = webHostBuilder.Configure((context, app) =>
{
// 注册应用中间件组件
foreach (var (componentType, opt) in options.ApplicationComponents)
{
app.UseComponent(context.HostingEnvironment, componentType, opt);
}
});
}
});
builder = builder.ConfigureServices(services =>
{
// 调用自定义配置服务
options?.ActionServices?.Invoke(services);
});
// 构建主机
app = builder.Build();
InternalApp.RootServices ??= app.Services;
var applicationPartManager = app.Services.GetService<ApplicationPartManager>();
applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name));
// 配置所有 Starup Configure
UseStartups(app.Services);
// 释放内存
App.AppStartups.Clear();
// 调用自定义配置
options?.ActionConfigure?.Invoke(app);
}
/// <summary>
/// 构建 IHost 对象
/// </summary>
@@ -908,7 +1061,6 @@ public static class Serve
&& u.GetParameters().Length > 0
&& u.GetParameters().First().ParameterType == typeof(IServiceProvider));
if (!configureMethods.Any()) continue;
// 自动安装属性调用
foreach (var method in configureMethods)
@@ -935,7 +1087,6 @@ public static class Serve
&& u.GetParameters().Length > 0
&& u.GetParameters().First().ParameterType == typeof(IApplicationBuilder));
if (!configureMethods.Any()) continue;
// 自动安装属性调用
foreach (var method in configureMethods)

View File

@@ -11,7 +11,6 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
#pragma warning disable CA1822 // 将成员标记为 static
namespace ThingsGateway;

View File

@@ -150,18 +150,18 @@ public static class DependencyInjectionServiceCollectionExtensions
{
Register(services, dependencyType, type, injectionAttribute);
}
if (!canInjectInterfaces.Any()) return;
var list = canInjectInterfaces.ToList();
if (list.Count == 0) return;
// 只注册第一个接口
if (injectionAttribute.Pattern is InjectionPatterns.FirstInterface or InjectionPatterns.SelfWithFirstInterface)
{
Register(services, dependencyType, type, injectionAttribute, canInjectInterfaces.Last());
Register(services, dependencyType, type, injectionAttribute, list.Last());
}
// 注册多个接口
else if (injectionAttribute.Pattern is InjectionPatterns.ImplementedInterfaces or InjectionPatterns.All)
{
foreach (var inter in canInjectInterfaces)
foreach (var inter in list)
{
Register(services, dependencyType, type, injectionAttribute, inter);
}

View File

@@ -47,12 +47,14 @@ internal sealed class NamedServiceProvider<TService> : INamedServiceProvider<TSe
{
var services = _serviceProvider.GetServices<TService>();
#pragma warning disable CA1851
if (services
.OfType<AspectDispatchProxy>()
.FirstOrDefault(u => ResovleServiceName(((dynamic)u).Target.GetType()) == serviceName) is not TService service)
{
service = services.FirstOrDefault(u => ResovleServiceName(u.GetType()) == serviceName);
}
#pragma warning restore CA1851
return service;
}
@@ -80,12 +82,14 @@ internal sealed class NamedServiceProvider<TService> : INamedServiceProvider<TSe
// 解析所有实现
var services = _serviceProvider.GetServices<TService>();
#pragma warning disable CA1851
if (services
.OfType<AspectDispatchProxy>()
.FirstOrDefault(u => ResovleServiceName(((dynamic)u).Target.GetType()) == serviceName) is not TService service)
{
service = services.FirstOrDefault(u => ResovleServiceName(u.GetType()) == serviceName);
}
#pragma warning restore CA1851
// 如果服务不存在,抛出异常
return service ?? throw new InvalidOperationException($"Named service `{serviceName}` is not registered in container.");

View File

@@ -356,7 +356,7 @@ internal sealed class EventBusHostedService : BackgroundService
GC.WaitForPendingFinalizers();
}
}
}, stoppingToken);
}, stoppingToken, TaskCreationOptions.None, TaskScheduler.Default);
});
}

View File

@@ -60,7 +60,7 @@ internal sealed partial class ChannelEventPublisher : IEventPublisher
await Task.Delay(TimeSpan.FromMilliseconds(delay), eventSource.CancellationToken).ConfigureAwait(false);
await _eventSourceStorer.WriteAsync(eventSource, eventSource.CancellationToken).ConfigureAwait(false);
}, eventSource.CancellationToken);
}, eventSource.CancellationToken, TaskCreationOptions.None, TaskScheduler.Default);
return Task.CompletedTask;
}

View File

@@ -38,6 +38,7 @@ public static class IEndpointRouteBuilderExtensions
&& u.IsDefined(typeof(MapHubAttribute), true)
&& (typeof(Hub).IsAssignableFrom(u) || u.HasImplementedRawGeneric(typeof(Hub<>))));
#pragma warning disable CA1851
if (!hubs.Any()) return;
// 反射获取 MapHub 拓展方法
@@ -65,5 +66,6 @@ public static class IEndpointRouteBuilderExtensions
hub.GetMethod("HubEndpointConventionBuilderSettings", BindingFlags.Public | BindingFlags.Static)
?.Invoke(null, new object[] { hubEndpointConventionBuilder });
}
#pragma warning restore CA1851
}
}

View File

@@ -127,7 +127,8 @@ public sealed class DatabaseLogger : ILogger, IDisposable
// 设置日志消息模板
logMsg.Message = _options.MessageFormat != null
? _options.MessageFormat(logMsg)
: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame, provider: _options.FormatProvider);
: string.Empty;
//: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame, provider: _options.FormatProvider);
// 空检查
if (logMsg.Message is null)

View File

@@ -162,8 +162,8 @@ public sealed class DatabaseLoggerProvider : ILoggerProvider, ISupportExternalSc
_databaseLoggingWriter = _serviceScope.ServiceProvider.GetRequiredService(databaseLoggingWriterType) as IDatabaseLoggingWriter;
// 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中
_processQueueTask = Task.Factory.StartNew(ProcessQueueAsync
, TaskCreationOptions.LongRunning);
_processQueueTask = Task.Factory.StartNew(ProcessQueueAsync, CancellationToken.None
, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
/// <summary>

View File

@@ -91,7 +91,7 @@ public sealed class FileLoggerProvider : ILoggerProvider, ISupportExternalScope
_fileLoggingWriter = new FileLoggingWriter(this);
// 创建长时间运行的后台任务,并将日志消息队列中数据写入文件中
_processQueueTask = Task.Factory.StartNew(ProcessQueueAsync, TaskCreationOptions.LongRunning);
_processQueueTask = Task.Factory.StartNew(ProcessQueueAsync, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
/// <summary>

View File

@@ -85,6 +85,7 @@ public static class OptionsBuilderExtensions
var builderInterfaces = optionsBuilderType.GetInterfaces()
.Where(u => optionsBuilderDependency.IsAssignableFrom(u) && u != optionsBuilderDependency);
#pragma warning disable CA1851
if (!builderInterfaces.Any())
{
return optionsBuilder;
@@ -95,6 +96,7 @@ public static class OptionsBuilderExtensions
{
InvokeMapMethod(optionsBuilder, optionsBuilderType, builderInterface);
}
#pragma warning restore CA1851
return optionsBuilder;
}

View File

@@ -115,7 +115,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
if (Persistence is not null)
{
// 创建长时间运行的后台任务,并将作业运行消息写入持久化中
_processQueueTask = Task.Factory.StartNew(ProcessQueueAsync, TaskCreationOptions.LongRunning);
_processQueueTask = Task.Factory.StartNew(ProcessQueueAsync, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
}
@@ -163,19 +163,16 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
var initialSchedulerBuilders = _schedulerBuilders.Concat(preloadSchedulerBuilders ?? Enumerable.Empty<SchedulerBuilder>());
// 如果作业调度器中包含作业计划构建器
if (initialSchedulerBuilders.Any())
// 逐条遍历并加载到内存中
foreach (var schedulerBuilder in initialSchedulerBuilders)
{
// 逐条遍历并加载到内存中
foreach (var schedulerBuilder in initialSchedulerBuilders)
SchedulerBuilder schedulerBuilderObj = null;
if (isSetPersistence)
{
SchedulerBuilder schedulerBuilderObj = null;
if (isSetPersistence)
{
schedulerBuilderObj = await Persistence.OnLoadingAsync(schedulerBuilder, stoppingToken).ConfigureAwait(false);
}
_ = TrySaveJob(schedulerBuilderObj ?? schedulerBuilder, out _, false);
schedulerBuilderObj = await Persistence.OnLoadingAsync(schedulerBuilder, stoppingToken).ConfigureAwait(false);
}
_ = TrySaveJob(schedulerBuilderObj ?? schedulerBuilder, out _, false);
}
}
catch (Exception ex)
@@ -484,11 +481,12 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
.Where(t => t.NextShouldRun(startAt))
.Select(t => t.NextRunTime.Value));
// 空检查
#pragma warning disable CA1851
if (!nextRunTimes.Any()) return null;
// 获取最早触发的时间
var earliestTriggerTime = nextRunTimes.Min();
#pragma warning restore CA1851
// 计算总休眠时间
var sleepMilliseconds = (earliestTriggerTime - startAt).TotalMilliseconds;

View File

@@ -400,7 +400,7 @@ internal sealed class ScheduleHostedService : BackgroundService
// 通知 GC 垃圾回收器回收
//_schedulerFactory.GCCollect();
}
}, stoppingToken);
}, stoppingToken, TaskCreationOptions.None, TaskScheduler.Default);
});
}
});

View File

@@ -31,6 +31,7 @@
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="$(NET9Version)" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
@@ -43,6 +44,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="$(NET9Version)" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(NET9Version)" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(NET9Version)" />
</ItemGroup>

View File

@@ -31,10 +31,8 @@ internal readonly struct ComponentMetadata
Version = version?.ToString();
Description = description;
#pragma warning disable CA1863 // 使用 "CompositeFormat"
NuGetPage = string.Format(Constants.NUGET_PACKAGE_PAGE, name, version?.ToString() ?? string.Empty);
DocumentationPage = string.Format(Constants.FURION_COMPONENT_DOCS_PAGE, Name);
#pragma warning restore CA1863 // 使用 "CompositeFormat"
}
/// <summary>

View File

@@ -546,7 +546,9 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
else
{
// ReSharper disable once MethodHasAsyncOverload
#pragma warning disable CA1849
InvokeStatusCodeHandlers(httpRequestBuilder, httpResponseMessage, timeoutCancellationToken);
#pragma warning restore CA1849
}
// 检查 HTTP 响应内容长度是否在设定的最大缓冲区大小限制内

View File

@@ -232,7 +232,7 @@ public abstract class Cache : DisposeBase, ICache
/// <returns></returns>
public virtual Int64 Increment(String key, Int64 value)
{
lock (this)
lock (lockThis)
{
var v = Get<Int64>(key);
v += value;
@@ -248,7 +248,7 @@ public abstract class Cache : DisposeBase, ICache
/// <returns></returns>
public virtual Double Increment(String key, Double value)
{
lock (this)
lock (lockThis)
{
var v = Get<Double>(key);
v += value;
@@ -264,7 +264,7 @@ public abstract class Cache : DisposeBase, ICache
/// <returns></returns>
public virtual Int64 Decrement(String key, Int64 value)
{
lock (this)
lock (lockThis)
{
var v = Get<Int64>(key);
v -= value;
@@ -273,14 +273,14 @@ public abstract class Cache : DisposeBase, ICache
return v;
}
}
protected object lockThis = new();
/// <summary>递减,原子操作</summary>
/// <param name="key">键</param>
/// <param name="value">变化量</param>
/// <returns></returns>
public virtual Double Decrement(String key, Double value)
{
lock (this)
lock (lockThis)
{
var v = Get<Double>(key);
v -= value;

View File

@@ -865,7 +865,7 @@ public class MemoryCache : Cache
/// <inheritdoc/>
public override void HashAdd<T>(string key, string hashKey, T value)
{
lock (this)
lock (lockThis)
{
//获取字典
var exist = GetDictionary<T>(key);
@@ -879,7 +879,7 @@ public class MemoryCache : Cache
/// <inheritdoc/>
public override bool HashSet<T>(string key, Dictionary<string, T> dic)
{
lock (this)
lock (lockThis)
{
//获取字典
var exist = GetDictionary<T>(key);

View File

@@ -80,7 +80,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
{
if (_inited) return;
lock (this)
lock (lockThis)
{
if (_inited) return;
_inited = true;
@@ -274,6 +274,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
/// <returns></returns>
protected virtual T? OnCreate() => (T?)typeof(T).CreateInstance();
#endregion
protected object lockThis = new();
#region
private TimerX? _timer;
@@ -281,7 +282,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
private void StartTimer()
{
if (_timer != null) return;
lock (this)
lock (lockThis)
{
if (_timer != null) return;

View File

@@ -25,6 +25,7 @@ public class Pool<T> : IPool<T> where T : class
public T? Value;
}
#endregion
protected object lockThis = new();
#region
/// <summary>实例化对象池。默认大小CPU*2</summary>
@@ -63,7 +64,7 @@ public class Pool<T> : IPool<T> where T : class
private void Init()
{
if (_items != null) return;
lock (this)
lock (lockThis)
{
if (_items != null) return;

View File

@@ -18,6 +18,7 @@ public sealed class IncrementCount
private long _current = 0;
private long _max = long.MaxValue;
private long _start = 0;
private object lockThis = new();
/// <inheritdoc cref="IncrementCount"/>
public IncrementCount(long max, long start = 0, int tick = 1)
@@ -43,7 +44,7 @@ public sealed class IncrementCount
/// </summary>
public long GetCurrentValue()
{
lock (this)
lock (lockThis)
{
long current = _current;
_current += IncreaseTick;
@@ -65,7 +66,7 @@ public sealed class IncrementCount
/// </summary>
public void ResetCurrentValue()
{
lock (this)
lock (lockThis)
{
_current = _start;
}
@@ -77,7 +78,7 @@ public sealed class IncrementCount
/// <param name="value">指定值</param>
public void ResetCurrentValue(long value)
{
lock (this)
lock (lockThis)
{
_current = value <= _max ? value >= _start ? value : _start : _max;
}
@@ -88,7 +89,7 @@ public sealed class IncrementCount
/// </summary>
public void ResetMaxValue(long max)
{
lock (this)
lock (lockThis)
{
if (max > _start)
{
@@ -108,7 +109,7 @@ public sealed class IncrementCount
/// <param name="start">初始值</param>
public void ResetStartValue(long start)
{
lock (this)
lock (lockThis)
{
if (start < _max)
{

View File

@@ -132,17 +132,6 @@ public class MachineInfo : IExtend
/// <summary>机器信息提供者。外部实现可修改部分行为</summary>
public static IMachineInfo? Provider { get; set; }
//static MachineInfo() => RegisterAsync().Wait(100);
private static Task<MachineInfo>? _task;
/// <summary>异步注册一个初始化后的机器信息实例</summary>
/// <returns></returns>
public static Task<MachineInfo> RegisterAsync()
{
if (_task != null) return _task;
return _task = Task.Factory.StartNew(() => Register());
}
private static MachineInfo Register()
{
@@ -694,13 +683,20 @@ public class MachineInfo : IExtend
if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty())
Memory = (UInt64)str.TrimEnd(" kB").ToLong();
ulong ma = 0;
if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty())
AvailableMemory = (UInt64)str.TrimEnd(" kB").ToLong();
else if (dic.TryGetValue("MemFree", out str) && !str.IsNullOrEmpty())
AvailableMemory =
(UInt64)(str.TrimEnd(" kB").ToLong() +
dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0 +
dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0);
{
ma = (UInt64)(str.TrimEnd(" kB").ToLong());
}
//低于3.14内核的版本用 free+cache
var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() ?? 0);
var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0);
var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0);
var free = mf + mc + bf;
AvailableMemory = ma > free ? ma : free;
}
// A2/A4温度获取BuildrootCPU温度和主板温度

View File

@@ -39,7 +39,7 @@ public class Config<TConfig> where TConfig : Config<TConfig>, new()
Provider = prv;
}
private static readonly Object _lock = new Object();
private static TConfig? _Current;
/// <summary>当前实例。通过置空可以使其重新加载。</summary>
public static TConfig Current
@@ -47,7 +47,7 @@ public class Config<TConfig> where TConfig : Config<TConfig>, new()
get
{
if (_Current != null) return _Current;
lock (typeof(TConfig))
lock (_lock)
{
if (_Current != null) return _Current;

View File

@@ -110,7 +110,7 @@ public abstract class FileConfigProvider : ConfigProvider
if (model == null) return false;
// 加锁,避免多线程冲突
lock (this)
lock (lockThis)
{
// 文件存储直接覆盖Root
Root.Childs?.Clear();
@@ -168,7 +168,7 @@ public abstract class FileConfigProvider : ConfigProvider
private void InitTimer()
{
if (_timer != null) return;
lock (this)
lock (lockThis)
{
if (_timer != null) return;

View File

@@ -180,6 +180,7 @@ public abstract class ConfigProvider : DisposeBase, IConfigProvider
/// <param name="value"></param>
public virtual void Init(String value) { }
#endregion
protected object lockThis = new();
#region /
/// <summary>从数据源加载数据到配置树</summary>
@@ -189,7 +190,7 @@ public abstract class ConfigProvider : DisposeBase, IConfigProvider
private void EnsureLoad()
{
if (_Loaded) return;
lock (this)
lock (lockThis)
{
if (_Loaded) return;

View File

@@ -84,13 +84,14 @@ public class Snowflake
}
}
#endregion
protected object lockThis = new();
#region
private Boolean _inited;
private void Init()
{
if (_inited) return;
lock (this)
lock (lockThis)
{
if (_inited) return;
@@ -152,7 +153,7 @@ public class Snowflake
// 核心理念:时间不同时序号置零,时间相同时序号递增
var seq = 0;
lock (this)
lock (lockThis)
{
while (true)
{
@@ -180,7 +181,7 @@ public class Snowflake
//{
// if (ms > origin)
// {
// lock (this)
// lock (lockThis)
// {
// origin = Volatile.Read(ref _lastTime);
// if (ms > origin)
@@ -209,7 +210,7 @@ public class Snowflake
// origin = Volatile.Read(ref _lastTime);
// if (ms == origin)
// {
// lock (this)
// lock (lockThis)
// {
// origin = Volatile.Read(ref _lastTime);
// if (ms == origin)

View File

@@ -0,0 +1,9 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Reliability", "CA1849", Justification = "<挂起>", Scope = "module")]

View File

@@ -67,6 +67,7 @@ public class CsvDb<T> : DisposeBase where T : new()
_cache = null;
}
#endregion
protected object lockThis = new();
#region
/// <summary>批量写入数据(高性能)</summary>
@@ -74,7 +75,6 @@ public class CsvDb<T> : DisposeBase where T : new()
/// <param name="append">是否附加在尾部。为false时从头写入覆盖已有数据</param>
public void Write(IEnumerable<T> models, Boolean append)
{
if (!models.Any() && append) return;
var file = GetFile();
file.EnsureDirectory(true);
@@ -146,7 +146,7 @@ public class CsvDb<T> : DisposeBase where T : new()
{
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
lock (this)
lock (lockThis)
{
if (_cache != null) return _cache.RemoveAll(x => predicate(x));
@@ -190,7 +190,7 @@ public class CsvDb<T> : DisposeBase where T : new()
{
if (Comparer == null) throw new ArgumentNullException(nameof(Comparer));
lock (this)
lock (lockThis)
{
var list = _cache ?? FindAll();
if (!add && list.Count == 0) return false;
@@ -266,7 +266,7 @@ public class CsvDb<T> : DisposeBase where T : new()
var file = GetFile();
if (!File.Exists(file)) yield break;
lock (this)
lock (lockThis)
{
using var csv = new CsvFile(file, false) { Encoding = Encoding };
@@ -334,7 +334,7 @@ public class CsvDb<T> : DisposeBase where T : new()
{
if (_cache != null) return _cache!.Count;
lock (this)
lock (lockThis)
{
var file = GetFile();
if (!File.Exists(file)) return 0;

View File

@@ -25,7 +25,7 @@ public class ConsoleLog : Logger
return;
}
lock (this)
lock (lockThis)
{
var cc = Console.ForegroundColor;
cc = level switch

View File

@@ -271,4 +271,7 @@ public abstract class Logger : ILog
return sb.ToString();
}
#endregion
protected object lockThis = new();
}

View File

@@ -33,6 +33,7 @@ public class PerfCounter : DisposeBase, ICounter
_Timer.TryDispose();
}
#endregion
protected object lockThis = new();
#region
/// <summary>增加</summary>
@@ -49,7 +50,7 @@ public class PerfCounter : DisposeBase, ICounter
if (_Timer == null)
{
lock (this)
lock (lockThis)
{
_Timer ??= new TimerX(DoWork, null, Interval, Interval) { Async = true };
}

View File

@@ -43,6 +43,7 @@ public class PacketCodec
/// <summary>APM性能追踪器</summary>
public ITracer? Tracer { get; set; }
#endregion
protected object lockThis = new();
/// <summary>数据包加入缓存数据末尾,分析数据流,得到一帧或多帧数据</summary>
/// <param name="pk">待分析数据包</param>
@@ -96,7 +97,7 @@ public class PacketCodec
}
// 加锁,避免多线程冲突
lock (this)
lock (lockThis)
{
// 检查缓存,内部可能创建或清空
CheckCache();

View File

@@ -102,6 +102,7 @@ public abstract class Actor : DisposeBase, IActor
/// <returns></returns>
public override String ToString() => Name;
#endregion
protected object lockThis = new();
#region
@@ -114,7 +115,7 @@ public abstract class Actor : DisposeBase, IActor
public virtual Task? Start(CancellationToken cancellationToken = default)
{
if (Active) return _task;
lock (this)
lock (lockThis)
{
if (Active) return _task;
@@ -128,7 +129,7 @@ public abstract class Actor : DisposeBase, IActor
// 启动异步
if (_task == null)
{
lock (this)
lock (lockThis)
{
_task ??= OnStart(_source.Token);
}

View File

@@ -55,6 +55,7 @@ public class DeferredQueue : DisposeBase
/// <summary>批次处理失败时</summary>
public Action<IList<Object>, Exception>? Error;
#endregion
protected object lockThis = new();
#region
/// <summary>实例化</summary>
@@ -76,7 +77,7 @@ public class DeferredQueue : DisposeBase
// 首次使用时初始化定时器
if (_Timer == null)
{
lock (this)
lock (lockThis)
{
_Timer ??= OnInit();
}

View File

@@ -32,6 +32,7 @@ public class DefaultMatchQueue : IMatchQueue
private readonly ItemWrap[] Items;
private Int32 _Count;
private TimerX? _Timer;
protected object lockThis = new();
/// <summary>按指定大小来初始化队列</summary>
/// <param name="size"></param>
@@ -76,7 +77,7 @@ public class DefaultMatchQueue : IMatchQueue
if (_Timer == null)
{
lock (this)
lock (lockThis)
{
_Timer ??= new TimerX(Check, null, 1000, 1000, "Match")
{

View File

@@ -93,6 +93,7 @@ public abstract class SessionBase : DisposeBase, ISocketClient, ITransport, ILog
public override String ToString() => Local + string.Empty;
#endregion
protected object lockThis = new();
#region
@@ -101,14 +102,14 @@ public abstract class SessionBase : DisposeBase, ISocketClient, ITransport, ILog
public virtual Boolean Open()
{
if (Active) return true;
if (!Monitor.TryEnter(this, Timeout + 100)) return false;
if (!Monitor.TryEnter(lockThis, Timeout + 100)) return false;
try
{
return OpenAsync().ConfigureAwait(false).GetAwaiter().GetResult();
}
finally
{
Monitor.Exit(this);
Monitor.Exit(lockThis);
}
}
@@ -177,14 +178,14 @@ public abstract class SessionBase : DisposeBase, ISocketClient, ITransport, ILog
public virtual Boolean Close(String reason)
{
if (!Active) return true;
if (!Monitor.TryEnter(this, Timeout + 100)) return false;
if (!Monitor.TryEnter(lockThis, Timeout + 100)) return false;
try
{
return CloseAsync(reason).ConfigureAwait(false).GetAwaiter().GetResult();
}
finally
{
Monitor.Exit(this);
Monitor.Exit(lockThis);
}
}

View File

@@ -240,7 +240,7 @@ public class TcpServer : DisposeBase, ISocketServer, ILogFeature
if (io)
ProcessAccept(se);
else
Task.Factory.StartNew(() => ProcessAccept(se));
Task.Factory.StartNew(() => ProcessAccept(se), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
}
return true;

View File

@@ -149,7 +149,7 @@ public class FullRedis : Redis
public void InitCluster()
{
if (_initCluster) return;
lock (this)
lock (lockThis)
{
if (_initCluster) return;

View File

@@ -53,6 +53,8 @@ public abstract class QueueBase : RedisBase
}
#endregion
protected object lockThis = new();
#region
/// <summary>验证失败</summary>
/// <param name="span"></param>

View File

@@ -263,14 +263,14 @@ public class RedisReliableQueue<T> : QueueBase, IProducerConsumer<T>, IDisposabl
{
if (_delay == null)
{
lock (this)
lock (lockThis)
{
_delay ??= new RedisDelayQueue<T>(Redis, $"{Key}:Delay");
}
}
if (_delayTask?.IsCompleted != false)
{
lock (this)
lock (lockThis)
{
if (_delayTask?.IsCompleted != false)
{

View File

@@ -387,7 +387,7 @@ public abstract class Redis : Cache, IConfigMapping, ILogFeature
get
{
if (_Pool != null) return _Pool;
lock (this)
lock (lockThis)
{
if (_Pool != null) return _Pool;

View File

@@ -53,6 +53,7 @@ public class RedisDeferred : DisposeBase
_timer.TryDispose();
}
#endregion
protected object lockThis = new();
#region
/// <summary>放入队列。后面定时批量处理</summary>
@@ -68,7 +69,7 @@ public class RedisDeferred : DisposeBase
{
if (_timer == null)
{
lock (this)
lock (lockThis)
{
if (_timer == null)
{

View File

@@ -47,7 +47,7 @@ public class RedisStat : DisposeBase
_redis2.Log = _redis.Log;
_queue = _redis2.GetReliableQueue<String>(name);
_source = new CancellationTokenSource();
Task.Factory.StartNew(() => _queue.ConsumeAsync<String>(OnProcess, _source.Token, null));
Task.Factory.StartNew(() => _queue.ConsumeAsync<String>(OnProcess, _source.Token, null), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
}
/// <summary>销毁</summary>

View File

@@ -1,4 +1,6 @@
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using ThingsGateway.NewLife.Common;
using ThingsGateway.NewLife.Configuration;
@@ -13,7 +15,7 @@ namespace ThingsGateway.NewLife;
/// 文档 https://newlifex.com/core/setting
/// </remarks>
[DisplayName("核心设置")]
[Config("Core")]
[Config("LogConfig", Provider = "json")]
public class Setting : Config<Setting>
{
#region
@@ -23,6 +25,7 @@ public class Setting : Config<Setting>
/// <summary>日志等级只输出大于等于该级别的日志All/Debug/Info/Warn/Error/Fatal默认Info</summary>
[Description("日志等级。只输出大于等于该级别的日志All/Debug/Info/Warn/Error/Fatal默认Info")]
[XmlIgnore, IgnoreDataMember]
public LogLevel LogLevel { get; set; } = LogLevel.Info;
/// <summary>文件日志目录。默认Log子目录</summary>
@@ -43,30 +46,37 @@ public class Setting : Config<Setting>
/// <summary>日志行格式。默认Time|ThreadId|Kind|Name|Message还支持Level</summary>
[Description("日志行格式。默认Time|ThreadId|Kind|Name|Message还支持Level")]
[XmlIgnore, IgnoreDataMember]
public String LogLineFormat { get; set; } = "Time|ThreadId|Kind|Name|Message";
/// <summary>网络日志。本地子网日志广播udp://255.255.255.255:514或者http://xxx:80/log</summary>
[Description("网络日志。本地子网日志广播udp://255.255.255.255:514或者http://xxx:80/log")]
[XmlIgnore, IgnoreDataMember]
public String NetworkLog { get; set; } = string.Empty;
/// <summary>日志记录时间UTC校正单位小时。默认0表示使用的是本地时间使用UTC时间的系统转换成本地时间则相差8小时</summary>
[Description("日志记录时间UTC校正小时")]
[XmlIgnore, IgnoreDataMember]
public Int32 UtcIntervalHours { get; set; } = 0;
/// <summary>数据目录。本地数据库目录默认Data子目录</summary>
[Description("数据目录。本地数据库目录默认Data子目录")]
[XmlIgnore, IgnoreDataMember]
public String DataPath { get; set; } = string.Empty;
/// <summary>备份目录。备份数据库时存放的目录默认Backup子目录</summary>
[Description("备份目录。备份数据库时存放的目录默认Backup子目录")]
[XmlIgnore, IgnoreDataMember]
public String BackupPath { get; set; } = string.Empty;
/// <summary>插件目录</summary>
[Description("插件目录")]
[XmlIgnore, IgnoreDataMember]
public String PluginPath { get; set; } = string.Empty;
/// <summary>辅助解析程序集。程序集加载过程中被依赖程序集未能解析时是否协助解析默认false</summary>
[Description("辅助解析程序集。程序集加载过程中被依赖程序集未能解析时是否协助解析默认false")]
[XmlIgnore, IgnoreDataMember]
public Boolean AssemblyResolve { get; set; }
#endregion

View File

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

View File

@@ -62,6 +62,7 @@ public class TimerScheduler : ILogFeature
private TimerX[] Timers = [];
#endregion
protected object lockThis = new();
/// <summary>把定时器加入队列</summary>
/// <param name="timer"></param>
public void Add(TimerX timer)
@@ -73,7 +74,7 @@ public class TimerScheduler : ILogFeature
timer.Id = Interlocked.Increment(ref _tid);
WriteLog("Timer.Add {0}", timer);
lock (this)
lock (lockThis)
{
var list = new List<TimerX>(Timers);
if (list.Contains(timer)) return;
@@ -109,7 +110,7 @@ public class TimerScheduler : ILogFeature
using var span = DefaultTracer.Instance?.NewSpan("timer:Remove", reason + " " + timer);
WriteLog("Timer.Remove {0} reason:{1}", timer, reason);
lock (this)
lock (lockThis)
{
timer.Id = 0;
@@ -172,7 +173,7 @@ public class TimerScheduler : ILogFeature
// 必须在主线程设置状态,否则可能异步线程还没来得及设置开始状态,主线程又开始了新的一轮调度
timer.Calling = true;
if (timer.IsAsyncTask)
Task.Factory.StartNew(ExecuteAsync, timer);
Task.Factory.StartNew(ExecuteAsync, timer, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
else if (!timer.Async)
Execute(timer);
else
@@ -305,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

@@ -1,121 +0,0 @@
using System.Reflection;
using ThingsGateway.NewLife.Log;
namespace ThingsGateway.NewLife.Web;
/// <summary>插件助手</summary>
public static class PluginHelper
{
/// <summary>创建客户端。便于外部自定义</summary>
public static Func<String, WebClientX> CreateClient { get; set; } = linkName => new WebClientX { AuthKey = "NewLife", Log = XTrace.Log };
/// <summary>加载插件</summary>
/// <param name="typeName">插件类型</param>
/// <param name="disname">显示名</param>
/// <param name="dll">DLL文件</param>
/// <param name="linkName">链接名。要在下载页面中搜索的链接名</param>
/// <param name="urls">提供下载地址的多个目标页面</param>
/// <returns></returns>
public static Type? LoadPlugin(String typeName, String? disname, String? dll, String? linkName, String? urls = null)
{
if (typeName.IsNullOrEmpty()) throw new ArgumentNullException(nameof(typeName));
//var type = typeName.GetTypeEx(true);
var type = Type.GetType(typeName);
if (type != null) return type;
if (dll.IsNullOrEmpty()) return null;
var set = Setting.Current;
var file = string.Empty;
if (!dll.IsNullOrEmpty())
{
// 先检查程序集所在目录,再检查当前目录、基准目录和插件目录。在应用发布时,插件很可能跟常规应用程序集放在同一目录下
var exe = Assembly.GetEntryAssembly()?.Location;
if (exe.IsNullOrEmpty()) exe = Assembly.GetCallingAssembly()?.Location;
if (exe.IsNullOrEmpty()) exe = Assembly.GetExecutingAssembly()?.Location;
if (!exe.IsNullOrEmpty()) file = Path.GetDirectoryName(exe).CombinePath(dll).GetFullPath();
if (!File.Exists(file)) file = dll.GetCurrentPath();
if (!File.Exists(file)) file = dll.GetFullPath();
if (!File.Exists(file)) file = dll.GetBasePath();
if (!File.Exists(file)) file = set.PluginPath.CombinePath(dll).GetFullPath();
if (!File.Exists(file)) file = set.PluginPath.CombinePath(dll).GetBasePath();
}
// 尝试直接加载DLL
if (File.Exists(file))
{
try
{
var asm = Assembly.LoadFrom(file);
type = asm.GetType(typeName);
if (type != null) return type;
}
catch (Exception ex)
{
XTrace.WriteException(ex);
}
}
if (linkName.IsNullOrEmpty()) return null;
// 按类型名锁定,超时取不到锁,则放弃
if (!Monitor.TryEnter(typeName, 15_000)) return null;
try
{
type = Type.GetType(typeName);
if (type != null) return type;
//if (urls.IsNullOrEmpty()) urls = set.PluginServer;
// 如果本地没有数据库,则从网络下载
if (!File.Exists(file))
{
XTrace.WriteLine("{0}不存在或平台版本不正确,准备联网获取 {1}", !disname.IsNullOrEmpty() ? disname : dll, urls);
//var client = new WebClientX()
//{
// Log = XTrace.Log
//};
using var client = CreateClient(linkName);
var dir = Path.GetDirectoryName(file);
var file2 = client.DownloadLinkAndExtract(urls, linkName, dir!, true);
//client.TryDispose();
}
if (!File.Exists(file))
{
XTrace.WriteLine("未找到 {0} {1}", disname, dll);
return null;
}
//return Assembly.LoadFrom(file).GetType(typeName);
type = Type.GetType(typeName);
if (type != null) return type;
// 尝试直接加载DLL
if (File.Exists(file))
{
try
{
var asm = Assembly.LoadFrom(file);
type = asm.GetType(typeName);
if (type != null) return type;
}
catch (Exception ex)
{
XTrace.WriteException(ex);
}
}
return null;
}
finally
{
Monitor.Exit(typeName);
}
}
}

View File

@@ -22,9 +22,11 @@ namespace ThingsGateway.NewLife.Xml;
/// <typeparam name="TConfig"></typeparam>
public class XmlConfig<TConfig> : DisposeBase where TConfig : XmlConfig<TConfig>, new()
{
protected object lockThis = new();
#region
private static Boolean _loading;
private static TConfig? _Current;
private static WaitLock _WaitLock = new WaitLock(typeof(TConfig).FullName ?? "XmlConfig");
/// <summary>当前实例。通过置空可以使其重新加载。</summary>
public static TConfig Current
{
@@ -58,7 +60,8 @@ public class XmlConfig<TConfig> : DisposeBase where TConfig : XmlConfig<TConfig>
}
// 现在没有对象尝试加载若返回null则实例化一个新的
lock (dcf)
_WaitLock.Wait();
try
{
if (_Current != null) return _Current;
@@ -87,7 +90,10 @@ public class XmlConfig<TConfig> : DisposeBase where TConfig : XmlConfig<TConfig>
}
}
}
finally
{
_WaitLock.Release();
}
return config;
}
set { _Current = value; }
@@ -295,7 +301,7 @@ public class XmlConfig<TConfig> : DisposeBase where TConfig : XmlConfig<TConfig>
filename = filename.GetBasePath();
// 加锁避免多线程保存同一个文件冲突
lock (filename)
lock (lockThis)
{
var xml1 = File.Exists(filename) ? File.ReadAllText(filename).Trim() : null;
var xml2 = GetXml();
@@ -326,7 +332,7 @@ public class XmlConfig<TConfig> : DisposeBase where TConfig : XmlConfig<TConfig>
{
if (_Timer == null)
{
lock (this)
lock (lockThis)
{
_Timer ??= new TimerX(DoSave, null, 1000, 5000)
{

View File

@@ -101,7 +101,9 @@ namespace Photino.Blazor
while (true)
{
var message = await reader.ReadAsync();
#pragma warning disable CA1849
_window.SendWebMessage(message);
#pragma warning restore CA1849
}
}
catch (ChannelClosedException) { }

View File

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

View File

@@ -0,0 +1,6 @@
@namespace ThingsGateway.Razor
@if (show)
{
<Spinner class="ms-auto"></Spinner>
}

View File

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

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