Compare commits

...

33 Commits

Author SHA1 Message Date
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
687 changed files with 8585 additions and 9658 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

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

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

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

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

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);
}
@@ -68,34 +68,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 +115,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";
@@ -127,12 +128,12 @@ 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));
db.CopyNew().DbMaintenance.GetColumnInfosByTableName(tableName));
// 构建schema信息
columns.ForEach(d =>
@@ -170,8 +171,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");
@@ -184,7 +185,7 @@ namespace ThingsGateway.SqlSugar
// 发送请求并处理响应
var httpResponseMessage =
await Post(client, name, httpContent).ConfigureAwait(false);
await Post(client, tableName, httpContent).ConfigureAwait(false);
var readAsStringAsync = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync);
@@ -266,11 +267,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 +282,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

@@ -23,7 +23,7 @@ namespace ThingsGateway.SqlSugar
/// <param name="propertyInfo">可选的属性信息数组,若为空则通过反射获取</param>
/// <param name="sqlParameterKeyWord">SQL参数前缀例如 "@"</param>
/// <returns>SugarParameter 数组</returns>
protected virtual IReadOnlyList<SugarParameter> GetParameters(object parameters, PropertyInfo[] propertyInfo, string sqlParameterKeyWord)
protected virtual IReadOnlyCollection<SugarParameter> GetParameters(object parameters, PropertyInfo[] propertyInfo, string sqlParameterKeyWord)
{
if (parameters != null)
{
@@ -37,7 +37,7 @@ namespace ThingsGateway.SqlSugar
// 字典类型转参数
return DictionaryToParameters(parameters, sqlParameterKeyWord, entityType);
}
else if (parameters is IReadOnlyList<SugarParameter> sugarParamArray)
else if (parameters is IReadOnlyCollection<SugarParameter> sugarParamArray)
{
// 如果是 SugarParameter 数组,转 List
return sugarParamArray;
@@ -96,7 +96,7 @@ namespace ThingsGateway.SqlSugar
/// <summary>
/// 将字典类型的参数转换为参数集合
/// </summary>
protected IReadOnlyList<SugarParameter> DictionaryToParameters(object parameters, string sqlParameterKeyWord, Type entityType)
protected IReadOnlyCollection<SugarParameter> DictionaryToParameters(object parameters, string sqlParameterKeyWord, Type entityType)
{
if (entityType == UtilConstants.DicArraySO)
{

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