Compare commits
	
		
			47 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					515bdb9700 | ||
| 
						 | 
					46c1780017 | ||
| 
						 | 
					fe78a4c3ca | ||
| 
						 | 
					2d7effadf9 | ||
| 
						 | 
					346c560f8b | ||
| 
						 | 
					8e3bd89f61 | ||
| 
						 | 
					6da142d080 | ||
| 
						 | 
					ff7d029e6f | ||
| 
						 | 
					21b4695683 | ||
| 
						 | 
					02ad494a26 | ||
| 
						 | 
					280366e1b2 | ||
| 
						 | 
					6660ce3e34 | ||
| 
						 | 
					7499162c1a | ||
| 
						 | 
					40208a5cd6 | ||
| 
						 | 
					fa347f4f68 | ||
| 
						 | 
					d7df6fc605 | ||
| 
						 | 
					eb4bb2fd48 | ||
| 
						 | 
					faa9858974 | ||
| 
						 | 
					1b3d2dda49 | ||
| 
						 | 
					a8a9453611 | ||
| 
						 | 
					e84f42ce14 | ||
| 
						 | 
					6f814cf6b8 | ||
| 
						 | 
					e36432e4e9 | ||
| 
						 | 
					ebd71e807b | ||
| 
						 | 
					34000d8d7d | ||
| 
						 | 
					e785f6660c | ||
| 
						 | 
					831c611797 | ||
| 
						 | 
					453817ef86 | ||
| 
						 | 
					8ce0b981c1 | ||
| 
						 | 
					4e5c51b54c | ||
| 
						 | 
					3cc9d31f28 | ||
| 
						 | 
					10391f869b | ||
| 
						 | 
					fba0723a6d | ||
| 
						 | 
					2db3f78f0c | ||
| 
						 | 
					badf61fe01 | ||
| 
						 | 
					d74e0952dc | ||
| 
						 | 
					fb1699ce80 | ||
| 
						 | 
					44adddbcd4 | ||
| 
						 | 
					0eab889452 | ||
| 
						 | 
					e14d39a459 | ||
| 
						 | 
					7575264ede | ||
| 
						 | 
					3e1a077b96 | ||
| 
						 | 
					a921cb8400 | ||
| 
						 | 
					4de7c31ed7 | ||
| 
						 | 
					08326a2cfd | ||
| 
						 | 
					e045de5acb | ||
| 
						 | 
					d3bef31aa6 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -364,6 +364,7 @@ FodyWeavers.xsd
 | 
			
		||||
 | 
			
		||||
/src/*Pro*/
 | 
			
		||||
/src/*Pro*
 | 
			
		||||
/src/**/*Pro*
 | 
			
		||||
/src/*pro*
 | 
			
		||||
/src/*pro*/
 | 
			
		||||
/src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@
 | 
			
		||||
		<IncludeBuildOutput>false</IncludeBuildOutput>
 | 
			
		||||
		<!-- 避免 DLL 被打包到 lib/ -->
 | 
			
		||||
		<EnableSourceGenerator>true</EnableSourceGenerator>
 | 
			
		||||
		
 | 
			
		||||
		<!-- 可选 -->
 | 
			
		||||
 | 
			
		||||
		
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -37,9 +37,8 @@ public class FileController : ControllerBase
 | 
			
		||||
        var root = Directory.GetCurrentDirectory();
 | 
			
		||||
        var wwwroot = Path.Combine(root, "wwwroot");
 | 
			
		||||
        var filePath = Path.Combine(wwwroot, fileName);
 | 
			
		||||
        // 防止路径穿越攻击
 | 
			
		||||
#pragma warning disable CA3003
 | 
			
		||||
        if (!filePath.StartsWith(wwwroot, StringComparison.OrdinalIgnoreCase) || !System.IO.File.Exists(filePath))
 | 
			
		||||
        if ((!(fileName.StartsWith(@"../Logs") || fileName.StartsWith(@"..\Logs")) && filePath.Contains("..")) || !System.IO.File.Exists(filePath))
 | 
			
		||||
        {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
@@ -49,6 +48,6 @@ public class FileController : ControllerBase
 | 
			
		||||
 | 
			
		||||
        Response.Headers.Append("Access-Control-Expose-Headers", "Content-Disposition");
 | 
			
		||||
 | 
			
		||||
        return File(fileStream, "application/octet-stream", (fileName.Replace('/', '_')));
 | 
			
		||||
        return File(fileStream, "application/octet-stream", (Path.GetFileName(filePath).Replace('/', '_')));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,16 +21,7 @@
 | 
			
		||||
    "UserNoModule": "This account has not been assigned a module. Please contact the administrator",
 | 
			
		||||
    "UserNull": "User {0} does not exist"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Application.BaseDataEntity": {
 | 
			
		||||
    "CreateOrgId": "CreateOrgId"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Application.BaseEntity": {
 | 
			
		||||
    "CreateTime": "CreateTime",
 | 
			
		||||
    "CreateUser": "CreateUser",
 | 
			
		||||
    "SortCode": "SortCode",
 | 
			
		||||
    "UpdateTime": "UpdateTime",
 | 
			
		||||
    "UpdateUser": "UpdateUser"
 | 
			
		||||
  },
 | 
			
		||||
  
 | 
			
		||||
  "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
 | 
			
		||||
    "UserExpire": "User expired, please login again"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -21,16 +21,7 @@
 | 
			
		||||
    "UserNoModule": "该账号未分配模块,请联系管理员",
 | 
			
		||||
    "UserNull": "用户 {0} 不存在"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Application.BaseDataEntity": {
 | 
			
		||||
    "CreateOrgId": "创建机构Id"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Application.BaseEntity": {
 | 
			
		||||
    "CreateTime": "创建时间",
 | 
			
		||||
    "CreateUser": "创建人",
 | 
			
		||||
    "SortCode": "排序",
 | 
			
		||||
    "UpdateTime": "更新时间",
 | 
			
		||||
    "UpdateUser": "更新人"
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
 | 
			
		||||
    "UserExpire": "用户登录已过期,请重新登录"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Gateway.Application;
 | 
			
		||||
namespace ThingsGateway.Admin.Application;
 | 
			
		||||
 | 
			
		||||
public class USheetDatas
 | 
			
		||||
{
 | 
			
		||||
@@ -18,6 +18,7 @@ public class SessionOutput : PrimaryIdEntity
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 主键Id
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [System.ComponentModel.DataAnnotations.Key]
 | 
			
		||||
    public override long Id { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -377,9 +377,9 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
 | 
			
		||||
    /// 获取用户拥有的资源
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="id">用户id</param>
 | 
			
		||||
    public async Task<GrantResourceData> OwnResourceAsync(long id)
 | 
			
		||||
    public Task<GrantResourceData> OwnResourceAsync(long id)
 | 
			
		||||
    {
 | 
			
		||||
        return await _roleService.OwnResourceAsync(id, RelationCategoryEnum.UserHasResource).ConfigureAwait(false);
 | 
			
		||||
        return _roleService.OwnResourceAsync(id, RelationCategoryEnum.UserHasResource);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -505,10 +505,10 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
 | 
			
		||||
        var password = await GetDefaultPassWord(true).ConfigureAwait(false);//获取默认密码,这里不走Aop所以需要加密一下
 | 
			
		||||
        using var db = GetDB();
 | 
			
		||||
        //重置密码
 | 
			
		||||
        if (await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
 | 
			
		||||
        if ((await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
 | 
			
		||||
        {
 | 
			
		||||
            Password = password
 | 
			
		||||
        }, it => it.Id == id).ConfigureAwait(false))
 | 
			
		||||
        }, it => it.Id == id).ConfigureAwait(false)) > 0)
 | 
			
		||||
        {
 | 
			
		||||
            DeleteUserFromCache(id);//从cache删除用户信息
 | 
			
		||||
            var verificatInfoIds = _verificatInfoService.GetListByUserId(id);
 | 
			
		||||
 
 | 
			
		||||
@@ -185,12 +185,12 @@ internal sealed class UserCenterService : BaseService<SysUser>, IUserCenterServi
 | 
			
		||||
        using var db = GetDB();
 | 
			
		||||
 | 
			
		||||
        //更新指定字段
 | 
			
		||||
        var result = await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
 | 
			
		||||
        var result = (await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
 | 
			
		||||
        {
 | 
			
		||||
            Email = input.Email,
 | 
			
		||||
            Phone = input.Phone,
 | 
			
		||||
            Avatar = input.Avatar,
 | 
			
		||||
        }, it => it.Id == UserManager.UserId).ConfigureAwait(false);
 | 
			
		||||
        }, it => it.Id == UserManager.UserId).ConfigureAwait(false)) > 0;
 | 
			
		||||
        if (result)
 | 
			
		||||
            _userService.DeleteUserFromCache(UserManager.UserId);//cache删除用户数据
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<GenerateDocumentationFile>True</GenerateDocumentationFile>
 | 
			
		||||
		
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -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
 | 
			
		||||
{
 | 
			
		||||
@@ -30,7 +30,7 @@ public class BlazorAppContext
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 全部菜单
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public IEnumerable<SysResource> AllMenus { get; private set; }
 | 
			
		||||
    public List<SysResource> AllMenus { get; private set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 当前用户
 | 
			
		||||
@@ -42,22 +42,22 @@ public class BlazorAppContext
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 用户个人菜单
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public IEnumerable<MenuItem> OwnMenuItems { get; private set; }
 | 
			
		||||
    public List<MenuItem> OwnMenuItems { get; private set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 不同模块的菜单
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public IEnumerable<MenuItem> AllOwnMenuItems { get; private set; }
 | 
			
		||||
    public List<MenuItem> AllOwnMenuItems { get; private set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 用户个人菜单,多个模块
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public IEnumerable<SysResource> OwnMenus { get; private set; }
 | 
			
		||||
    public List<SysResource> OwnMenus { get; private set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 用户个人菜单,非树形
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public IEnumerable<MenuItem> OwnSameLevelMenuItems { get; private set; }
 | 
			
		||||
    public List<MenuItem> OwnSameLevelMenuItems { get; private set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 个人工作台
 | 
			
		||||
@@ -67,9 +67,9 @@ public class BlazorAppContext
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 用户个人快捷方式菜单
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public IEnumerable<SysResource> UserWorkbenchOutputs { get; private set; }
 | 
			
		||||
    public List<SysResource> UserWorkbenchOutputs { get; private set; }
 | 
			
		||||
 | 
			
		||||
    public IEnumerable<SysResource> AllResource { get; private set; }
 | 
			
		||||
    public List<SysResource> AllResource { get; private set; }
 | 
			
		||||
 | 
			
		||||
    private ISysResourceService ResourceService { get; }
 | 
			
		||||
    private ISysUserService SysUserService { get; }
 | 
			
		||||
@@ -93,7 +93,7 @@ public class BlazorAppContext
 | 
			
		||||
            AllResource = sysResources;
 | 
			
		||||
            var ids = CurrentUser.ModuleList.Select(a => a.Id).ToHashSet();
 | 
			
		||||
            CurrentUser.ModuleList = AllResource.Where(a => ids.Contains(a.Id)).OrderBy(a => a.SortCode).ToList();
 | 
			
		||||
            AllMenus = AllResource.Where(a => a.Category == ResourceCategoryEnum.Menu);
 | 
			
		||||
            AllMenus = AllResource.Where(a => a.Category == ResourceCategoryEnum.Menu).ToList();
 | 
			
		||||
 | 
			
		||||
            if (moduleId == null)
 | 
			
		||||
            {
 | 
			
		||||
@@ -123,8 +123,8 @@ public class BlazorAppContext
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            var ownMenus = OwnMenus.Where(a => a.Module == CurrentModuleId);
 | 
			
		||||
            OwnMenuItems = ResourceUtil.BuildMenuTrees(ownMenus).ToList();
 | 
			
		||||
            AllOwnMenuItems = ResourceUtil.BuildMenuTrees(OwnMenus).ToList();
 | 
			
		||||
            OwnMenuItems = AdminResourceUtil.BuildMenuTrees(ownMenus).ToList();
 | 
			
		||||
            AllOwnMenuItems = AdminResourceUtil.BuildMenuTrees(OwnMenus).ToList();
 | 
			
		||||
            OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item => new MenuItem()
 | 
			
		||||
            {
 | 
			
		||||
                Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All,
 | 
			
		||||
@@ -132,8 +132,8 @@ public class BlazorAppContext
 | 
			
		||||
                Icon = item.Icon,
 | 
			
		||||
                Url = item.Href,
 | 
			
		||||
                Target = item.Target.ToString(),
 | 
			
		||||
            });
 | 
			
		||||
            UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id));
 | 
			
		||||
            }).ToList();
 | 
			
		||||
            UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)).ToList();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ public partial class EditPagePolicy
 | 
			
		||||
 | 
			
		||||
    protected override Task OnParametersSetAsync()
 | 
			
		||||
    {
 | 
			
		||||
        ShortcutsTreeViewItems = ResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null);
 | 
			
		||||
        ShortcutsTreeViewItems = AdminResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null);
 | 
			
		||||
        return base.OnParametersSetAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -48,6 +48,6 @@ public partial class EditPagePolicy
 | 
			
		||||
    {
 | 
			
		||||
        await Task.CompletedTask;
 | 
			
		||||
        ShortcutsSearchText = searchText;
 | 
			
		||||
        return ResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null);
 | 
			
		||||
        return AdminResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ public partial class MenuChoiceDialog
 | 
			
		||||
        var all = (await SysResourceService.GetAllAsync());
 | 
			
		||||
        var items = all.Where(a => a.Category == ResourceCategoryEnum.Menu && a.Module == ModuleId);
 | 
			
		||||
        ModuleTitle = all.FirstOrDefault(a => a.Id == ModuleId)?.Title;
 | 
			
		||||
        Items = ResourceUtil.BuildTreeItemList(items, new List<long> { Value }, RenderTreeItem);
 | 
			
		||||
        Items = AdminResourceUtil.BuildTreeItemList(items, new List<long> { Value }, RenderTreeItem);
 | 
			
		||||
        await base.OnParametersSetAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,12 +26,12 @@
 | 
			
		||||
                OnQueryAsync="OnQueryAsync" CustomerSearchModel="@CustomerSearchModel"
 | 
			
		||||
                OnSaveAsync="Save" OnDeleteAsync="Delete">
 | 
			
		||||
        <TableToolbarTemplate>
 | 
			
		||||
            <PopConfirmButton Color=Color.Warning IsDisabled="SelectedRows.Count<=0||!AuthorizeButton(AdminOperConst.Add)" Text=@OperDescLocalizer["CopyResource"] Icon="fa fa-copy" OnConfirm="OnCopy">
 | 
			
		||||
            <PopConfirmButton Color=Color.Warning IsKeepDisabled="SelectedRows.Count <= 0 || !AuthorizeButton(AdminOperConst.Add)" Text=@OperDescLocalizer["CopyResource"] Icon="fa fa-copy" OnConfirm="OnCopy">
 | 
			
		||||
                <BodyTemplate>
 | 
			
		||||
                    <Select Items="ModuleSelectedItems" @bind-Value=CopyModule ShowLabel="false" />
 | 
			
		||||
                </BodyTemplate>
 | 
			
		||||
            </PopConfirmButton>
 | 
			
		||||
            <PopConfirmButton Color=Color.Warning IsDisabled="SelectedRows.Count!=1||!AuthorizeButton(AdminOperConst.Edit)" Text=@OperDescLocalizer["ChangeParentResource"] Icon="fa fa-copy" OnConfirm="OnChangeParent">
 | 
			
		||||
            <PopConfirmButton Color=Color.Warning IsKeepDisabled="SelectedRows.Count != 1 || !AuthorizeButton(AdminOperConst.Edit)" Text=@OperDescLocalizer["ChangeParentResource"] Icon="fa fa-copy" OnConfirm="OnChangeParent">
 | 
			
		||||
                <BodyTemplate>
 | 
			
		||||
                    <div class="overflow-y-auto" style="height:500px">
 | 
			
		||||
                        <TreeView Items="MenuTreeItems" IsVirtualize="true" OnTreeItemClick="a=>{ChangeParentId=a.Value.Id;return Task.CompletedTask;}" />
 | 
			
		||||
 
 | 
			
		||||
@@ -39,8 +39,8 @@ public partial class SysResourcePage
 | 
			
		||||
 | 
			
		||||
    protected override async Task OnParametersSetAsync()
 | 
			
		||||
    {
 | 
			
		||||
        ModuleSelectedItems = ResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
 | 
			
		||||
        MenuItems = ResourceUtil.BuildMenuSelectList((await SysResourceService.GetAllAsync())).Concat(new List<SelectedItem>() { new("0", AdminLocalizer["Root"]) }).ToList();
 | 
			
		||||
        ModuleSelectedItems = AdminResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
 | 
			
		||||
        MenuItems = AdminResourceUtil.BuildMenuSelectList((await SysResourceService.GetAllAsync())).Concat(new List<SelectedItem>() { new("0", AdminLocalizer["Root"]) }).ToList();
 | 
			
		||||
 | 
			
		||||
        await base.OnParametersSetAsync();
 | 
			
		||||
    }
 | 
			
		||||
@@ -49,7 +49,7 @@ public partial class SysResourcePage
 | 
			
		||||
 | 
			
		||||
    private async Task<QueryData<SysResource>> OnQueryAsync(QueryPageOptions options)
 | 
			
		||||
    {
 | 
			
		||||
        MenuTreeItems = new List<TreeViewItem<SysResource>>() { new TreeViewItem<SysResource>(new SysResource()) { Text = AdminLocalizer["Root"] } }.Concat(ResourceUtil.BuildTreeItemList((await SysResourceService.GetAllAsync()).Where(a => a.Module == CustomerSearchModel.Module), new(), null)).ToList();
 | 
			
		||||
        MenuTreeItems = new List<TreeViewItem<SysResource>>() { new TreeViewItem<SysResource>(new SysResource()) { Text = AdminLocalizer["Root"] } }.Concat(AdminResourceUtil.BuildTreeItemList((await SysResourceService.GetAllAsync()).Where(a => a.Module == CustomerSearchModel.Module), new(), null)).ToList();
 | 
			
		||||
 | 
			
		||||
        var data = await SysResourceService.PageAsync(options, CustomerSearchModel);
 | 
			
		||||
        return data;
 | 
			
		||||
@@ -136,14 +136,14 @@ public partial class SysResourcePage
 | 
			
		||||
    private async Task<IEnumerable<TableTreeNode<SysResource>>> OnTreeExpand(SysResource menu)
 | 
			
		||||
    {
 | 
			
		||||
        var sysResources = await SysResourceService.GetAllAsync();
 | 
			
		||||
        var result = ResourceUtil.BuildTableTrees(sysResources, menu.Id);
 | 
			
		||||
        var result = AdminResourceUtil.BuildTableTrees(sysResources, menu.Id);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static async Task<IEnumerable<TableTreeNode<SysResource>>> TreeNodeConverter(IEnumerable<SysResource> items)
 | 
			
		||||
    {
 | 
			
		||||
        await Task.CompletedTask;
 | 
			
		||||
        var result = ResourceUtil.BuildTableTrees(items, 0);
 | 
			
		||||
        var result = AdminResourceUtil.BuildTableTrees(items, 0);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ public partial class GrantResourceDialog
 | 
			
		||||
    {
 | 
			
		||||
        var items = (await SysResourceService.GetAllAsync()).Where(a => a.Category != ResourceCategoryEnum.Module).OrderBy(a => a.Module).ThenBy(a => a.Id).ToList();
 | 
			
		||||
 | 
			
		||||
        Items = ResourceUtil.BuildTreeItemList(items, Value, RenderTreeItem);
 | 
			
		||||
        Items = AdminResourceUtil.BuildTreeItemList(items, Value, RenderTreeItem);
 | 
			
		||||
        ModuleList = (await SysResourceService.GetAllAsync()).Where(a => a.Category == ResourceCategoryEnum.Module).ToList();
 | 
			
		||||
        await base.OnInitializedAsync();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ public partial class SysUserEdit
 | 
			
		||||
        BoolItems = LocalizerUtil.GetBoolItems(Model.GetType(), nameof(Model.Status));
 | 
			
		||||
        var items = await SysPositionService.SelectorAsync(new PositionSelectorInput());
 | 
			
		||||
        Items = PositionUtil.BuildCascaderItemList(items);
 | 
			
		||||
        ModuleSelectedItems = ResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
 | 
			
		||||
        ModuleSelectedItems = AdminResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
 | 
			
		||||
        await InvokeAsync(StateHasChanged);
 | 
			
		||||
        await base.OnInitializedAsync();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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'">
 | 
			
		||||
@@ -17,6 +18,7 @@
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
 | 
			
		||||
		
 | 
			
		||||
		<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>-->
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Razor;
 | 
			
		||||
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
 | 
			
		||||
public static class ResourceUtil
 | 
			
		||||
public static class AdminResourceUtil
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 构造选择项,ID/TITLE
 | 
			
		||||
@@ -29,7 +29,7 @@
 | 
			
		||||
  <Target Name="AdminPostPublish" AfterTargets="Publish">
 | 
			
		||||
    <ItemGroup>
 | 
			
		||||
      <!-- setting up the variable for convenience -->
 | 
			
		||||
      <AdminFiles Include="bin\$(Configuration)\$(TargetFramework)\SeedData\**" />
 | 
			
		||||
      <AdminFiles Include="$(OutputPath)\$(TargetFramework)\SeedData\**" />
 | 
			
		||||
    </ItemGroup>
 | 
			
		||||
    <PropertyGroup>
 | 
			
		||||
    </PropertyGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,8 @@
 | 
			
		||||
#推送:docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway
 | 
			
		||||
 | 
			
		||||
#aspnetcore9.0环境
 | 
			
		||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
 | 
			
		||||
#FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
 | 
			
		||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble AS base
 | 
			
		||||
COPY .  /app
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
#默认web
 | 
			
		||||
@@ -13,6 +14,8 @@ EXPOSE 5000
 | 
			
		||||
 | 
			
		||||
# 添加时区环境变量,亚洲,上海
 | 
			
		||||
ENV TimeZone=Asia/Shanghai
 | 
			
		||||
# 转发头
 | 
			
		||||
ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
 | 
			
		||||
# 使用软连接,并且将时区配置覆盖/etc/timezone
 | 
			
		||||
RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,8 @@
 | 
			
		||||
#推送:docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64
 | 
			
		||||
 | 
			
		||||
#aspnetcore9.0环境
 | 
			
		||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine-arm64v8  AS base
 | 
			
		||||
#FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine-arm64v8  AS base
 | 
			
		||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble-arm64v8 AS base
 | 
			
		||||
COPY .  /app
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
#默认web
 | 
			
		||||
@@ -13,6 +14,8 @@ EXPOSE 5000
 | 
			
		||||
 | 
			
		||||
# 添加时区环境变量,亚洲,上海
 | 
			
		||||
ENV TimeZone=Asia/Shanghai
 | 
			
		||||
# 转发头
 | 
			
		||||
ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
 | 
			
		||||
# 使用软连接,并且将时区配置覆盖/etc/timezone
 | 
			
		||||
RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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 -->
 | 
			
		||||
 
 | 
			
		||||
@@ -45,11 +45,11 @@ public class Startup : AppStartup
 | 
			
		||||
            options.ServicesStopConcurrently = true;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        //// 事件总线
 | 
			
		||||
        //services.AddEventBus(options =>
 | 
			
		||||
        //{
 | 
			
		||||
        // 事件总线
 | 
			
		||||
        services.AddEventBus(options =>
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
        //});
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // 任务调度
 | 
			
		||||
        services.AddSchedule(options => options.AddPersistence<JobPersistence>());
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
 | 
			
		||||
		
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<!--<Import Project="Admin.targets" Condition=" '$(Configuration)' != 'Debug' " />-->
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Gateway.Application;
 | 
			
		||||
namespace ThingsGateway.Common;
 | 
			
		||||
 | 
			
		||||
public class SmartTriggerScheduler
 | 
			
		||||
{
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Gateway.Application;
 | 
			
		||||
namespace ThingsGateway.Common;
 | 
			
		||||
 | 
			
		||||
public sealed class StringOrdinalIgnoreCaseEqualityComparer : EqualityComparer<string>
 | 
			
		||||
{
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<GenerateDocumentationFile>True</GenerateDocumentationFile>
 | 
			
		||||
		
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
 | 
			
		||||
@@ -13,7 +14,7 @@
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
 | 
			
		||||
		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor" Version="9.8.2" />
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor" Version="9.9.3" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -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; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,8 @@ public static class QueryPageOptionsExtensions
 | 
			
		||||
        return datas;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static IEnumerable<T> GetQuery<T>(this IEnumerable<T> query, QueryPageOptions option, Func<IEnumerable<T>, IEnumerable<T>>? queryFunc = null, FilterKeyValueAction where = null)
 | 
			
		||||
    {
 | 
			
		||||
        if (queryFunc != null)
 | 
			
		||||
@@ -123,7 +125,36 @@ public static class QueryPageOptionsExtensions
 | 
			
		||||
        };
 | 
			
		||||
        var items = datas.GetData(option, out var totalCount, where);
 | 
			
		||||
        ret.TotalCount = totalCount;
 | 
			
		||||
 | 
			
		||||
        if (totalCount > 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (!items.Any() && option.PageIndex != 1)
 | 
			
		||||
            {
 | 
			
		||||
                option.PageIndex = 1;
 | 
			
		||||
                items = datas.GetData(option, out totalCount, where);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret.Items = items.ToList();
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 根据查询条件返回QueryData
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static QueryData<SelectedItem> GetQueryData<T>(this IEnumerable<T> datas, VirtualizeQueryOption option, Func<IEnumerable<T>, IEnumerable<SelectedItem>> func, FilterKeyValueAction where = null)
 | 
			
		||||
    {
 | 
			
		||||
        var ret = new QueryData<SelectedItem>()
 | 
			
		||||
        {
 | 
			
		||||
            IsSorted = false,
 | 
			
		||||
            IsFiltered = false,
 | 
			
		||||
            IsAdvanceSearch = false,
 | 
			
		||||
            IsSearch = !option.SearchText.IsNullOrWhiteSpace()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        var items = datas.Skip((option.StartIndex)).Take(option.Count);
 | 
			
		||||
        ret.TotalCount = datas.Count();
 | 
			
		||||
 | 
			
		||||
        ret.Items = func(items).ToList();
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -209,16 +209,10 @@ public static class SqlSugarExtensions
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public static async Task<bool> UpdateRangeAsync<T>(this SqlSugarClient db, List<T> updateObjs) where T : class, new()
 | 
			
		||||
    public static Task<int> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new()
 | 
			
		||||
    {
 | 
			
		||||
        return await db.Updateable(updateObjs).ExecuteCommandAsync().ConfigureAwait(false) > 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public static async Task<bool> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new()
 | 
			
		||||
    {
 | 
			
		||||
        return await db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression)
 | 
			
		||||
            .ExecuteCommandAsync().ConfigureAwait(false) > 0;
 | 
			
		||||
        return db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression)
 | 
			
		||||
            .ExecuteCommandAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static IEnumerable<T> Sort<T>(this IEnumerable<T> list, BasePageInput basePageInput)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								src/Admin/ThingsGateway.DB/Locales/en-US.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Admin/ThingsGateway.DB/Locales/en-US.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
  "ThingsGateway.Admin.Application.BaseDataEntity": {
 | 
			
		||||
    "CreateOrgId": "CreateOrgId"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Application.BaseEntity": {
 | 
			
		||||
    "CreateTime": "CreateTime",
 | 
			
		||||
    "CreateUser": "CreateUser",
 | 
			
		||||
    "SortCode": "SortCode",
 | 
			
		||||
    "UpdateTime": "UpdateTime",
 | 
			
		||||
    "UpdateUser": "UpdateUser"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/Admin/ThingsGateway.DB/Locales/zh-CN.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Admin/ThingsGateway.DB/Locales/zh-CN.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
{
 | 
			
		||||
  
 | 
			
		||||
  "ThingsGateway.DB.BaseDataEntity": {
 | 
			
		||||
    "CreateOrgId": "创建机构Id"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.DB.BaseEntity": {
 | 
			
		||||
    "CreateTime": "创建时间",
 | 
			
		||||
    "CreateUser": "创建人",
 | 
			
		||||
    "SortCode": "排序",
 | 
			
		||||
    "UpdateTime": "更新时间",
 | 
			
		||||
    "UpdateUser": "更新人"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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));
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<GenerateDocumentationFile>True</GenerateDocumentationFile>
 | 
			
		||||
		
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
 | 
			
		||||
@@ -18,6 +19,12 @@
 | 
			
		||||
		<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
	  <EmbeddedResource Include="Locales\en-US.json" />
 | 
			
		||||
	  <EmbeddedResource Include="Locales\zh-CN.json" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<!--<PackageReference Include="ThingsGateway.Razor" Version="$(SourceGeneratorVersion)" />-->
 | 
			
		||||
		<!--<ProjectReference Include="..\ThingsGateway.Razor\ThingsGateway.Razor.csproj" />-->
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    {
 | 
			
		||||
@@ -545,10 +554,9 @@ public static class App
 | 
			
		||||
        {
 | 
			
		||||
            types = ass.GetTypes();
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            XTrace.Log.Warn($"Error load `{ass.FullName}` assembly.");
 | 
			
		||||
            Console.WriteLine($"Error load `{ass.FullName}` assembly.");
 | 
			
		||||
            XTrace.Log.Warn($"Error load `{ass.FullName}` assembly. : {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return types.Where(u => u.IsPublic && !u.IsDefined(typeof(SuppressSnifferAttribute), false));
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ namespace ThingsGateway;
 | 
			
		||||
/// </summary>
 | 
			
		||||
public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions>
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 是否启用规范化文档
 | 
			
		||||
    /// </summary>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										341
									
								
								src/Admin/ThingsGateway.Furion/App/Options/MiniRunOptions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								src/Admin/ThingsGateway.Furion/App/Options/MiniRunOptions.cs
									
									
									
									
									
										Normal 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; }
 | 
			
		||||
}
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
 | 
			
		||||
		
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
@@ -31,6 +32,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 +45,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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -683,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温度获取,Buildroot,CPU温度和主板温度
 | 
			
		||||
 
 | 
			
		||||
@@ -190,7 +190,31 @@ public sealed class Crc32 //: HashAlgorithm
 | 
			
		||||
        crc.Update(stream, count);
 | 
			
		||||
        return crc.Value;
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 添加Sequence进行校验
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="sequence"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public Crc32 Update(ReadOnlySequence<byte> sequence)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var segment in sequence)
 | 
			
		||||
        {
 | 
			
		||||
            Update(segment.Span);
 | 
			
		||||
        }
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 计算校验码 (Sequence)
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="sequence"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static UInt32 Compute(ReadOnlySequence<byte> sequence)
 | 
			
		||||
    {
 | 
			
		||||
        var crc = new Crc32();
 | 
			
		||||
        crc.Update(sequence);
 | 
			
		||||
        return crc.Value;
 | 
			
		||||
    }
 | 
			
		||||
    //#region 抽象实现
 | 
			
		||||
    ///// <summary>哈希核心</summary>
 | 
			
		||||
    ///// <param name="array"></param>
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@
 | 
			
		||||
		<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
 | 
			
		||||
		<SignAssembly>True</SignAssembly>
 | 
			
		||||
		<AssemblyOriginatorKeyFile>newlife.snk</AssemblyOriginatorKeyFile>
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -306,8 +306,19 @@ public class TimerScheduler : ILogFeature
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var func = timer.Method.As<Func<Object?, Task>>(target);
 | 
			
		||||
            await func!(timer.State).ConfigureAwait(false);
 | 
			
		||||
#if NET6_0_OR_GREATER
 | 
			
		||||
            if (timer.IsValueTask)
 | 
			
		||||
            {
 | 
			
		||||
                var func = timer.Method.As<Func<Object?, ValueTask>>(target);
 | 
			
		||||
                await func!(timer.State).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
#endif
 | 
			
		||||
            {
 | 
			
		||||
                var func = timer.Method.As<Func<Object?, Task>>(target);
 | 
			
		||||
                await func!(timer.State).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        catch (ThreadAbortException) { throw; }
 | 
			
		||||
        catch (ThreadInterruptedException) { throw; }
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
 | 
			
		||||
		
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Common.Extension;
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
using ThingsGateway.Razor.Extension;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Razor;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
<Step @ref="@step" IsVertical="true">
 | 
			
		||||
    <StepItem Text=@Localizer["First"] Title=@Localizer["Upload"]>
 | 
			
		||||
        <InputUpload ShowDeleteButton="true" @bind-Value=_importFile Accept=".xlsx"></InputUpload>
 | 
			
		||||
        <Button class="mt-2" IsAsync OnClick="() => DeviceImport(_importFile)">@Localizer["Validate"]</Button>
 | 
			
		||||
        <PopConfirmButton IsAsync Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
 | 
			
		||||
    </StepItem>
 | 
			
		||||
    <StepItem Text=@Localizer["Second"] Title=@Localizer["ValidateText"]>
 | 
			
		||||
 | 
			
		||||
@@ -41,16 +41,12 @@
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            <PopConfirmButton IsAsync IsDisabled=@_importPreviews.Any(it => it.Value.HasError) Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
 | 
			
		||||
            <Button class="mt-2" IsAsync OnClick="() => DeviceImport()">@RazorLocalizer["Close"]</Button>
 | 
			
		||||
 | 
			
		||||
@* 
 | 
			
		||||
            <Button IsAsync class="mt-2" IsDisabled=@_importPreviews.Any(it => it.Value.HasError) OnClick="() => step.Next()">@Localizer["Next"]</Button> *@
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
    </StepItem>
 | 
			
		||||
@*     <StepItem Text=@Localizer["Third"] Title=@Localizer["Import"]>
 | 
			
		||||
        <PopConfirmButton IsAsync Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
 | 
			
		||||
    </StepItem> *@
 | 
			
		||||
 | 
			
		||||
</Step>
 | 
			
		||||
@code {
 | 
			
		||||
    [NotNull]
 | 
			
		||||
 
 | 
			
		||||
@@ -24,18 +24,17 @@ public partial class ImportExcel
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    [EditorRequired]
 | 
			
		||||
    public Func<Dictionary<string, ImportPreviewOutputBase>, Task> Import { get; set; }
 | 
			
		||||
    public Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> Import { get; set; }
 | 
			
		||||
 | 
			
		||||
    [Inject]
 | 
			
		||||
    [NotNull]
 | 
			
		||||
    private IStringLocalizer<ImportExcel>? Localizer { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 预览
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    [EditorRequired]
 | 
			
		||||
    public Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> Preview { get; set; }
 | 
			
		||||
    [Inject]
 | 
			
		||||
    [NotNull]
 | 
			
		||||
    private IStringLocalizer<ThingsGateway.Razor._Imports>? RazorLocalizer { get; set; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    [Inject]
 | 
			
		||||
    [NotNull]
 | 
			
		||||
@@ -47,13 +46,17 @@ public partial class ImportExcel
 | 
			
		||||
    [CascadingParameter]
 | 
			
		||||
    private Func<Task>? OnCloseAsync { get; set; }
 | 
			
		||||
 | 
			
		||||
    private async Task DeviceImport(IBrowserFile file)
 | 
			
		||||
    private async Task DeviceImport()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            _importPreviews.Clear();
 | 
			
		||||
            _importPreviews = await Preview.Invoke(file);
 | 
			
		||||
            await step.Next();
 | 
			
		||||
            await InvokeAsync(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                if (OnCloseAsync != null)
 | 
			
		||||
                    await OnCloseAsync();
 | 
			
		||||
                await ToastService.Default();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -67,16 +70,12 @@ public partial class ImportExcel
 | 
			
		||||
        {
 | 
			
		||||
            await Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                await Import.Invoke(_importPreviews);
 | 
			
		||||
                _importPreviews = await Import.Invoke(_importFile);
 | 
			
		||||
                _importFile = null;
 | 
			
		||||
 | 
			
		||||
                await InvokeAsync(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    if (OnCloseAsync != null)
 | 
			
		||||
                        await OnCloseAsync();
 | 
			
		||||
                    await ToastService.Default();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
            await step.Next();
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,58 @@
 | 
			
		||||
@using ThingsGateway.Extension
 | 
			
		||||
@namespace ThingsGateway.Razor
 | 
			
		||||
<Button OnClick="() => step.Reset()">@Localizer["Reset"]</Button>
 | 
			
		||||
<h6 class="my-3 green--text">@Localizer["Tip"] </h6>
 | 
			
		||||
<Step @ref="@step" IsVertical="true">
 | 
			
		||||
    <StepItem Text=@Localizer["First"] Title=@Localizer["Upload"]>
 | 
			
		||||
        <InputUpload ShowDeleteButton="true" @bind-Value=_importFile Accept=".xlsx"></InputUpload>
 | 
			
		||||
        <Button class="mt-2" IsAsync OnClick="() => DeviceImport(_importFile)">@Localizer["Validate"]</Button>
 | 
			
		||||
    </StepItem>
 | 
			
		||||
    <StepItem Text=@Localizer["Second"] Title=@Localizer["ValidateText"]>
 | 
			
		||||
 | 
			
		||||
        <div class="overflow-y-auto">
 | 
			
		||||
 | 
			
		||||
            @foreach (var item in _importPreviews)
 | 
			
		||||
            {
 | 
			
		||||
                <div class="mt-2">
 | 
			
		||||
                    @(
 | 
			
		||||
                                    Localizer["UploadCount", item.Key, item.Value.DataCount]
 | 
			
		||||
                                    )
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class=@((item.Value.HasError ? "my-2 red--text" : "my-2 green--text"))>
 | 
			
		||||
                    @(
 | 
			
		||||
                                    (item.Value.HasError ? "Error" : "Success")
 | 
			
		||||
                                    )
 | 
			
		||||
            </div>
 | 
			
		||||
                        if (item.Value.HasError)
 | 
			
		||||
                {
 | 
			
		||||
                    <div style="height:300px;" class="overflow-y-scroll">
 | 
			
		||||
                        <Virtualize Items="item.Value.Results.Where(a => !a.Success).OrderBy(a => a.Row).ToList()" Context="item1" ItemSize="60" OverscanCount=2>
 | 
			
		||||
                            <ItemContent>
 | 
			
		||||
                                <div class="row g-0">
 | 
			
		||||
                                    <span class="col mx-2">@item1.Row</span>
 | 
			
		||||
                                    <span class=@((item1.Success ? "green--text col-auto" : "red--text col-auto"))>
 | 
			
		||||
                                        <strong>@item1.ErrorMessage</strong>
 | 
			
		||||
                                    </span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </ItemContent>
 | 
			
		||||
                        </Virtualize>
 | 
			
		||||
                    </div>
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            <PopConfirmButton IsAsync IsKeepDisabled=@_importPreviews.Any(it => it.Value.HasError) Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
 | 
			
		||||
 | 
			
		||||
@* 
 | 
			
		||||
            <Button IsAsync class="mt-2" IsDisabled=@_importPreviews.Any(it => it.Value.HasError) OnClick="() => step.Next()">@Localizer["Next"]</Button> *@
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
    </StepItem>
 | 
			
		||||
@*     <StepItem Text=@Localizer["Third"] Title=@Localizer["Import"]>
 | 
			
		||||
        <PopConfirmButton IsAsync Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
 | 
			
		||||
    </StepItem> *@
 | 
			
		||||
</Step>
 | 
			
		||||
@code {
 | 
			
		||||
    [NotNull]
 | 
			
		||||
    Step? step { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,86 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://thingsgateway.cn/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using Microsoft.AspNetCore.Components.Forms;
 | 
			
		||||
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Razor;
 | 
			
		||||
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
public partial class ImportExcelConfirm
 | 
			
		||||
{
 | 
			
		||||
    private Dictionary<string, ImportPreviewOutputBase> _importPreviews = new();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 导入
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    [EditorRequired]
 | 
			
		||||
    public Func<Dictionary<string, ImportPreviewOutputBase>, Task> Import { get; set; }
 | 
			
		||||
 | 
			
		||||
    [Inject]
 | 
			
		||||
    [NotNull]
 | 
			
		||||
    private IStringLocalizer<ImportExcelConfirm>? Localizer { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 预览
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    [EditorRequired]
 | 
			
		||||
    public Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> Preview { get; set; }
 | 
			
		||||
 | 
			
		||||
    [Inject]
 | 
			
		||||
    [NotNull]
 | 
			
		||||
    private ToastService? ToastService { get; set; }
 | 
			
		||||
 | 
			
		||||
    [Required]
 | 
			
		||||
    private IBrowserFile _importFile { get; set; }
 | 
			
		||||
 | 
			
		||||
    [CascadingParameter]
 | 
			
		||||
    private Func<Task>? OnCloseAsync { get; set; }
 | 
			
		||||
 | 
			
		||||
    private async Task DeviceImport(IBrowserFile file)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            _importPreviews.Clear();
 | 
			
		||||
            _importPreviews = await Preview.Invoke(file);
 | 
			
		||||
            await step.Next();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            await ToastService.Warn(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task SaveDeviceImport()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                await Import.Invoke(_importPreviews);
 | 
			
		||||
                _importFile = null;
 | 
			
		||||
 | 
			
		||||
                await InvokeAsync(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    if (OnCloseAsync != null)
 | 
			
		||||
                        await OnCloseAsync();
 | 
			
		||||
                    await ToastService.Default();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            await InvokeAsync(async () => await ToastService.Warn(ex));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
::deep .avatar {
 | 
			
		||||
    border-radius: 1.5rem;
 | 
			
		||||
    width: 24px;
 | 
			
		||||
    height: 24px;
 | 
			
		||||
    background-color: var(--bs-red);
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    flex: 0 0 auto;
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
@namespace ThingsGateway.Razor
 | 
			
		||||
 | 
			
		||||
@if (show)
 | 
			
		||||
{
 | 
			
		||||
    <Spinner class="ms-auto"></Spinner>
 | 
			
		||||
}
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Gateway.Razor;
 | 
			
		||||
namespace ThingsGateway.Razor;
 | 
			
		||||
 | 
			
		||||
public partial class SpinnerComponent
 | 
			
		||||
{
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
 | 
			
		||||
using Microsoft.JSInterop;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Common.Extension;
 | 
			
		||||
namespace ThingsGateway.Razor.Extension;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// JSRuntime扩展方法
 | 
			
		||||
@@ -49,4 +49,28 @@ public static class JSRuntimeExtensions
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static async ValueTask<T> GetLocalStorage<T>(this IJSRuntime jsRuntime, string name)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            return await jsRuntime.InvokeAsync<T>("getLocalStorage", name).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
            return default;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async ValueTask SetLocalStorage<T>(this IJSRuntime jsRuntime, string name, T data)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await jsRuntime.InvokeVoidAsync("setLocalStorage", name, data).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -25,7 +25,8 @@
 | 
			
		||||
    "Success": "Success",
 | 
			
		||||
    "TablesExportButtonExcelText": "Export Excel",
 | 
			
		||||
    "TablesImportButtonExcelText": "Import Excel",
 | 
			
		||||
    "True": "Yes"
 | 
			
		||||
    "True": "Yes",
 | 
			
		||||
    "Info": "Info"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Razor.About": {
 | 
			
		||||
    "Community": "Community",
 | 
			
		||||
@@ -59,6 +60,19 @@
 | 
			
		||||
    "SearchText": "Search Page"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Razor.ImportExcel": {
 | 
			
		||||
    "First": "Step 1",
 | 
			
		||||
    "Import": "If there are no errors during verification, it will be directly imported into the database",
 | 
			
		||||
    "Next": "Next",
 | 
			
		||||
    "Reset": "Reset",
 | 
			
		||||
    "Second": "Step 2",
 | 
			
		||||
    "Third": "Step 3",
 | 
			
		||||
    "Tip": "When the data volume is large (more than 200,000), the import may take more than 1 minute, please be patient",
 | 
			
		||||
    "Upload": "Upload File",
 | 
			
		||||
    "UploadCount": " Table {0}, import {1} records",
 | 
			
		||||
    "Validate": "Validate",
 | 
			
		||||
    "ValidateText": "Validation Content"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Razor.ImportExcelConfirm": {
 | 
			
		||||
    "First": "Step 1",
 | 
			
		||||
    "Import": "Import",
 | 
			
		||||
    "Next": "Next",
 | 
			
		||||
@@ -67,7 +81,7 @@
 | 
			
		||||
    "Third": "Step 3",
 | 
			
		||||
    "Tip": "When the data volume is large (more than 200,000), the import may take more than 1 minute, please be patient",
 | 
			
		||||
    "Upload": "Upload File",
 | 
			
		||||
    "UploadCount": " Table {0}, expecting to import {1} records",
 | 
			
		||||
    "UploadCount": " Table {0}, import {1} records",
 | 
			
		||||
    "Validate": "Validate",
 | 
			
		||||
    "ValidateText": "Validation Content"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,8 @@
 | 
			
		||||
    "Success": "成功",
 | 
			
		||||
    "TablesExportButtonExcelText": "导出Excel",
 | 
			
		||||
    "TablesImportButtonExcelText": "导入Excel",
 | 
			
		||||
    "True": "是"
 | 
			
		||||
    "True": "是",
 | 
			
		||||
    "Info": "详情"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Razor.About": {
 | 
			
		||||
    "Community": "社区",
 | 
			
		||||
@@ -59,6 +60,19 @@
 | 
			
		||||
    "SearchText": "搜索页面"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Razor.ImportExcel": {
 | 
			
		||||
    "First": "第一步",
 | 
			
		||||
    "Import": "若验证无错误,将直接导入数据库",
 | 
			
		||||
    "Next": "下一步",
 | 
			
		||||
    "Reset": "重置",
 | 
			
		||||
    "Second": "第二步",
 | 
			
		||||
    "Third": "第三",
 | 
			
		||||
    "Tip": "数据量较大时(大于20万),所需导入时间可能超过1分钟,请耐心等待",
 | 
			
		||||
    "Upload": "上传文件",
 | 
			
		||||
    "UploadCount": " 表 {0},导入 {1} 条数据",
 | 
			
		||||
    "Validate": "验证",
 | 
			
		||||
    "ValidateText": "验证内容"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Razor.ImportExcelConfirm": {
 | 
			
		||||
    "First": "第一步",
 | 
			
		||||
    "Import": "导入",
 | 
			
		||||
    "Next": "下一步",
 | 
			
		||||
@@ -67,7 +81,7 @@
 | 
			
		||||
    "Third": "第三",
 | 
			
		||||
    "Tip": "数据量较大时(大于20万),所需导入时间可能超过1分钟,请耐心等待",
 | 
			
		||||
    "Upload": "上传文件",
 | 
			
		||||
    "UploadCount": " 表 {0},预计导入 {1} 条数据",
 | 
			
		||||
    "UploadCount": " 表 {0},导入 {1} 条数据",
 | 
			
		||||
    "Validate": "验证",
 | 
			
		||||
    "ValidateText": "验证内容"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
	<Import Project="..\..\PackNuget.props" />
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net8.0</TargetFrameworks>
 | 
			
		||||
		
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.1.0" />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
// 设置 culture
 | 
			
		||||
function setCultureLocalStorage(culture) {
 | 
			
		||||
    localStorage.setItem("culture", culture);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取 culture
 | 
			
		||||
function getCultureLocalStorage() {
 | 
			
		||||
    return localStorage.getItem("culture");
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/Admin/ThingsGateway.Razor/wwwroot/js/localStorageUtil.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Admin/ThingsGateway.Razor/wwwroot/js/localStorageUtil.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
// 设置 culture
 | 
			
		||||
function setCultureLocalStorage(culture) {
 | 
			
		||||
    localStorage.setItem("culture", culture);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取 culture
 | 
			
		||||
function getCultureLocalStorage() {
 | 
			
		||||
    return localStorage.getItem("culture");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 function getLocalStorage(name) {
 | 
			
		||||
    return JSON.parse(localStorage.getItem(name)) ?? 0;
 | 
			
		||||
}
 | 
			
		||||
 function setLocalStorage(name, data) {
 | 
			
		||||
    if (localStorage) {
 | 
			
		||||
        localStorage.setItem(name, JSON.stringify(data));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -28,16 +28,16 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
        /// 初始化 QuestDbRestAPI 实例
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="db">SqlSugar 数据库客户端</param>
 | 
			
		||||
        public QuestDbRestAPI(ISqlSugarClient db)
 | 
			
		||||
        /// <param name="httpPort">restApi端口</param>
 | 
			
		||||
        public QuestDbRestAPI(ISqlSugarClient db, int httpPort = 9000)
 | 
			
		||||
        {
 | 
			
		||||
            var builder = new DbConnectionStringBuilder();
 | 
			
		||||
            builder.ConnectionString = db.CurrentConnectionConfig.ConnectionString;
 | 
			
		||||
            this.db = db;
 | 
			
		||||
            string httpPort = String.Empty;
 | 
			
		||||
            string host = String.Empty;
 | 
			
		||||
            string username = String.Empty;
 | 
			
		||||
            string password = String.Empty;
 | 
			
		||||
            QuestDbRestAPHelper.SetRestApiInfo(builder, ref host, ref httpPort, ref username, ref password);
 | 
			
		||||
            QuestDbRestAPHelper.SetRestApiInfo(builder, ref host, ref username, ref password);
 | 
			
		||||
            BindHost(host, httpPort, username, password);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -51,9 +51,14 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            // HTTP GET 请求执行SQL
 | 
			
		||||
            var result = string.Empty;
 | 
			
		||||
            var url = $"{this.url}/exec?query={HttpUtility.UrlEncode(sql)}";
 | 
			
		||||
 | 
			
		||||
            var request = new HttpRequestMessage(HttpMethod.Get, url);
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(authorization))
 | 
			
		||||
                client.DefaultRequestHeaders.Add("Authorization", authorization);
 | 
			
		||||
            var httpResponseMessage = await client.GetAsync(url).ConfigureAwait(false);
 | 
			
		||||
            {
 | 
			
		||||
                request.Headers.Authorization = AuthenticationHeaderValue.Parse(authorization);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using var httpResponseMessage = await client.SendAsync(request).ConfigureAwait(false);
 | 
			
		||||
            result = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
@@ -68,34 +73,34 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            return ExecuteCommandAsync(sql).GetAwaiter().GetResult();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 异步批量插入单条数据
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <typeparam name="T">数据类型</typeparam>
 | 
			
		||||
        /// <param name="insertData">要插入的数据</param>
 | 
			
		||||
        /// <param name="dateFormat">日期格式字符串</param>
 | 
			
		||||
        /// <returns>影响的行数</returns>
 | 
			
		||||
        public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            if (db.CurrentConnectionConfig.MoreSettings == null)
 | 
			
		||||
                db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings();
 | 
			
		||||
            db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true;
 | 
			
		||||
            var sql = db.InsertableT(insertData).ToSqlString();
 | 
			
		||||
            var result = await ExecuteCommandAsync(sql).ConfigureAwait(false);
 | 
			
		||||
            return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
 | 
			
		||||
        }
 | 
			
		||||
        ///// <summary>
 | 
			
		||||
        ///// 异步批量插入单条数据
 | 
			
		||||
        ///// </summary>
 | 
			
		||||
        ///// <typeparam name="T">数据类型</typeparam>
 | 
			
		||||
        ///// <param name="insertData">要插入的数据</param>
 | 
			
		||||
        ///// <param name="dateFormat">日期格式字符串</param>
 | 
			
		||||
        ///// <returns>影响的行数</returns>
 | 
			
		||||
        //public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
 | 
			
		||||
        //{
 | 
			
		||||
        //    if (db.CurrentConnectionConfig.MoreSettings == null)
 | 
			
		||||
        //        db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings();
 | 
			
		||||
        //    db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true;
 | 
			
		||||
        //    var sql = db.InsertableT(insertData).ToSqlString();
 | 
			
		||||
        //    var result = await ExecuteCommandAsync(sql).ConfigureAwait(false);
 | 
			
		||||
        //    return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
 | 
			
		||||
        //}
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 同步批量插入单条数据
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <typeparam name="T">数据类型</typeparam>
 | 
			
		||||
        /// <param name="insertData">要插入的数据</param>
 | 
			
		||||
        /// <param name="dateFormat">日期格式字符串</param>
 | 
			
		||||
        /// <returns>影响的行数</returns>
 | 
			
		||||
        public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult();
 | 
			
		||||
        }
 | 
			
		||||
        ///// <summary>
 | 
			
		||||
        ///// 同步批量插入单条数据
 | 
			
		||||
        ///// </summary>
 | 
			
		||||
        ///// <typeparam name="T">数据类型</typeparam>
 | 
			
		||||
        ///// <param name="insertData">要插入的数据</param>
 | 
			
		||||
        ///// <param name="dateFormat">日期格式字符串</param>
 | 
			
		||||
        ///// <returns>影响的行数</returns>
 | 
			
		||||
        //public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
 | 
			
		||||
        //{
 | 
			
		||||
        //    return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult();
 | 
			
		||||
        //}
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 创建分页批量插入器
 | 
			
		||||
@@ -115,9 +120,10 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <typeparam name="T">数据类型</typeparam>
 | 
			
		||||
        /// <param name="insertList">要插入的数据列表</param>
 | 
			
		||||
        /// <param name="tableName">表名称</param>
 | 
			
		||||
        /// <param name="dateFormat">日期格式字符串</param>
 | 
			
		||||
        /// <returns>插入的记录数</returns>
 | 
			
		||||
        public async Task<int> BulkCopyAsync<T>(List<T> insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
 | 
			
		||||
        public async Task<int> BulkCopyAsync<T>(List<T> insertList, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            var result = 0;
 | 
			
		||||
            var fileName = $"{Guid.NewGuid()}.csv";
 | 
			
		||||
@@ -126,35 +132,43 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            {
 | 
			
		||||
                // 准备多部分表单数据
 | 
			
		||||
                var boundary = "---------------" + DateTime.Now.Ticks.ToString("x");
 | 
			
		||||
                var list = new List<Hashtable>();
 | 
			
		||||
                var name = db.EntityMaintenance.GetEntityInfo<T>().DbTableName;
 | 
			
		||||
 | 
			
		||||
                tableName ??= db.EntityMaintenance.GetEntityInfo<T>().DbTableName;
 | 
			
		||||
 | 
			
		||||
                // 获取或创建列信息缓存
 | 
			
		||||
                var key = "QuestDbBulkCopy" + typeof(T).FullName + typeof(T).GetHashCode();
 | 
			
		||||
                var columns = ReflectionInoCacheService.Instance.GetOrCreate(key, () =>
 | 
			
		||||
                 db.CopyNew().DbMaintenance.GetColumnInfosByTableName(name));
 | 
			
		||||
 | 
			
		||||
                // 构建schema信息
 | 
			
		||||
                columns.ForEach(d =>
 | 
			
		||||
                 db.CopyNew().DbMaintenance.GetColumnInfosByTableName(tableName));
 | 
			
		||||
                var list = ReflectionInoCacheService.Instance.GetOrCreate($"{key}{dateFormat}List<Hashtable>", () =>
 | 
			
		||||
                {
 | 
			
		||||
                    if (d.DataType == "TIMESTAMP")
 | 
			
		||||
                    var list = new List<Hashtable>();
 | 
			
		||||
 | 
			
		||||
                    // 构建schema信息
 | 
			
		||||
                    columns.ForEach(d =>
 | 
			
		||||
                    {
 | 
			
		||||
                        list.Add(new Hashtable()
 | 
			
		||||
                        if (d.DataType == "TIMESTAMP")
 | 
			
		||||
                        {
 | 
			
		||||
                            list.Add(new Hashtable()
 | 
			
		||||
                        {
 | 
			
		||||
                            { "name", d.DbColumnName },
 | 
			
		||||
                            { "type", d.DataType },
 | 
			
		||||
                            { "pattern", dateFormat}
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        list.Add(new Hashtable()
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            list.Add(new Hashtable()
 | 
			
		||||
                        {
 | 
			
		||||
                            { "name", d.DbColumnName },
 | 
			
		||||
                            { "type", d.DataType }
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    return list;
 | 
			
		||||
                }
 | 
			
		||||
 );
 | 
			
		||||
 | 
			
		||||
                var schema = JsonConvert.SerializeObject(list);
 | 
			
		||||
 | 
			
		||||
                // 写入CSV文件
 | 
			
		||||
@@ -170,8 +184,8 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
                // 准备HTTP请求内容
 | 
			
		||||
                using var httpContent = new MultipartFormDataContent(boundary);
 | 
			
		||||
                using var fileStream = File.OpenRead(filePath);
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(this.authorization))
 | 
			
		||||
                    client.DefaultRequestHeaders.Add("Authorization", this.authorization);
 | 
			
		||||
                //if (!string.IsNullOrWhiteSpace(this.authorization))
 | 
			
		||||
                //    client.DefaultRequestHeaders.Add("Authorization", this.authorization);
 | 
			
		||||
                httpContent.Add(new StringContent(schema), "schema");
 | 
			
		||||
                var streamContent = new StreamContent(fileStream);
 | 
			
		||||
                streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
 | 
			
		||||
@@ -183,8 +197,8 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
                    "multipart/form-data; boundary=" + boundary);
 | 
			
		||||
 | 
			
		||||
                // 发送请求并处理响应
 | 
			
		||||
                var httpResponseMessage =
 | 
			
		||||
                    await Post(client, name, httpContent).ConfigureAwait(false);
 | 
			
		||||
                using var httpResponseMessage =
 | 
			
		||||
                      await Post(client, tableName, httpContent).ConfigureAwait(false);
 | 
			
		||||
                var readAsStringAsync = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
 | 
			
		||||
                var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync);
 | 
			
		||||
 | 
			
		||||
@@ -266,11 +280,12 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <typeparam name="T">数据类型</typeparam>
 | 
			
		||||
        /// <param name="insertList">要插入的数据列表</param>
 | 
			
		||||
        /// <param name="tableName">表名称</param>
 | 
			
		||||
        /// <param name="dateFormat">日期格式字符串</param>
 | 
			
		||||
        /// <returns>插入的记录数</returns>
 | 
			
		||||
        public int BulkCopy<T>(List<T> insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
 | 
			
		||||
        public int BulkCopy<T>(List<T> insertList, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return BulkCopyAsync(insertList, dateFormat).GetAwaiter().GetResult();
 | 
			
		||||
            return BulkCopyAsync(insertList, tableName, dateFormat).GetAwaiter().GetResult();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@@ -280,7 +295,7 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
        /// <param name="httpPort">HTTP端口</param>
 | 
			
		||||
        /// <param name="username">用户名</param>
 | 
			
		||||
        /// <param name="password">密码</param>
 | 
			
		||||
        private void BindHost(string host, string httpPort, string username, string password)
 | 
			
		||||
        private void BindHost(string host, int httpPort, string username, string password)
 | 
			
		||||
        {
 | 
			
		||||
            url = host;
 | 
			
		||||
            if (url.EndsWith('/'))
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -111,7 +111,7 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
        /// <param name="context">SqlSugar提供者</param>
 | 
			
		||||
        /// <param name="dataRecord">数据记录器</param>
 | 
			
		||||
        /// <param name="fieldNames">字段名列表</param>
 | 
			
		||||
        public IDataReaderEntityBuilder(SqlSugarProvider context, IDataRecord dataRecord, List<string> fieldNames)
 | 
			
		||||
        public IDataReaderEntityBuilder(SqlSugarProvider context, IDataRecord dataRecord, IEnumerable<string> fieldNames)
 | 
			
		||||
        {
 | 
			
		||||
            this.Context = context;
 | 
			
		||||
            this.DataRecord = dataRecord;
 | 
			
		||||
 
 | 
			
		||||
@@ -679,7 +679,7 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
 | 
			
		||||
            {
 | 
			
		||||
                var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr,
 | 
			
		||||
                    columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T));
 | 
			
		||||
                    columns.Select(it => it.Item1)).CreateBuilder(typeof(T));
 | 
			
		||||
                return cacheResult;
 | 
			
		||||
            });
 | 
			
		||||
            using (dr)
 | 
			
		||||
@@ -706,7 +706,7 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
 | 
			
		||||
            {
 | 
			
		||||
                var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr,
 | 
			
		||||
                    columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T));
 | 
			
		||||
                    columns.Select(it => it.Item1)).CreateBuilder(typeof(T));
 | 
			
		||||
                return cacheResult;
 | 
			
		||||
            });
 | 
			
		||||
            if (cancellationToken.IsCancellationRequested) yield break;
 | 
			
		||||
@@ -743,7 +743,7 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
 | 
			
		||||
            {
 | 
			
		||||
                var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr,
 | 
			
		||||
                    columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T));
 | 
			
		||||
                    columns.Select(it => it.Item1)).CreateBuilder(typeof(T));
 | 
			
		||||
                return cacheResult;
 | 
			
		||||
            });
 | 
			
		||||
            using (dr)
 | 
			
		||||
@@ -775,7 +775,7 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
 | 
			
		||||
            {
 | 
			
		||||
                var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr,
 | 
			
		||||
                    columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T));
 | 
			
		||||
                    columns.Select(it => it.Item1)).CreateBuilder(typeof(T));
 | 
			
		||||
                return cacheResult;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -869,17 +869,13 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            InitMappingInfo<T>();
 | 
			
		||||
            return this.Context.Deleteable<T>().Where(expression);
 | 
			
		||||
        }
 | 
			
		||||
        public virtual IDeleteable<T> Deleteable<T>(dynamic primaryKeyValue) where T : class, new()
 | 
			
		||||
        public virtual IDeleteable<T> DeleteableById<T>(dynamic primaryKeyValue) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            InitMappingInfo<T>();
 | 
			
		||||
            return this.Context.Deleteable<T>().In(primaryKeyValue);
 | 
			
		||||
        }
 | 
			
		||||
        public virtual IDeleteable<T> Deleteable<T>(dynamic[] primaryKeyValues) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            InitMappingInfo<T>();
 | 
			
		||||
            return this.Context.Deleteable<T>().In(primaryKeyValues);
 | 
			
		||||
        }
 | 
			
		||||
        public virtual IDeleteable<T> Deleteable<T>(List<dynamic> pkValue) where T : class, new()
 | 
			
		||||
 | 
			
		||||
        public virtual IDeleteable<T> DeleteableById<T>(IReadOnlyCollection<dynamic> pkValue) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            InitMappingInfo<T>();
 | 
			
		||||
            return this.Context.Deleteable<T>().In(pkValue);
 | 
			
		||||
 
 | 
			
		||||
@@ -161,24 +161,20 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            return ScopedContext.Deleteable<T>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(dynamic primaryKeyValue) where T : class, new()
 | 
			
		||||
        public IDeleteable<T> DeleteableById<T>(dynamic primaryKeyValue) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return ScopedContext.Deleteable<T>(primaryKeyValue);
 | 
			
		||||
            return ScopedContext.DeleteableById<T>(primaryKeyValue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(dynamic[] primaryKeyValues) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return ScopedContext.Deleteable<T>(primaryKeyValues);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(Expression<Func<T, bool>> expression) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return ScopedContext.Deleteable(expression);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(List<dynamic> pkValue) where T : class, new()
 | 
			
		||||
        public IDeleteable<T> DeleteableById<T>(IReadOnlyCollection<dynamic> pkValue) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return ScopedContext.Deleteable<T>(pkValue);
 | 
			
		||||
            return ScopedContext.DeleteableById<T>(pkValue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(IReadOnlyCollection<T> deleteObjs) where T : class, new()
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,6 @@
 | 
			
		||||
        V Get<V>(string key);
 | 
			
		||||
        IEnumerable<string> GetAllKey<V>();
 | 
			
		||||
        void Remove<V>(string key);
 | 
			
		||||
        V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue);
 | 
			
		||||
        V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = 3600);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -31,7 +31,7 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            return ReflectionInoCore<V>.GetInstance().GetAllKey();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue)
 | 
			
		||||
        public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = 3600)
 | 
			
		||||
        {
 | 
			
		||||
            return ReflectionInoCore<V>.GetInstance().GetOrCreate(cacheKey, create);
 | 
			
		||||
        }
 | 
			
		||||
@@ -43,10 +43,13 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
    }
 | 
			
		||||
    public class ReflectionInoCore<V>
 | 
			
		||||
    {
 | 
			
		||||
        private MemoryCache InstanceCache => MemoryCache.Instance;
 | 
			
		||||
        private MemoryCache InstanceCache = new MemoryCache() { Expire = 180 };
 | 
			
		||||
        private static ReflectionInoCore<V> _instance = null;
 | 
			
		||||
        private static readonly object _instanceLock = new object();
 | 
			
		||||
        private ReflectionInoCore() { }
 | 
			
		||||
        private ReflectionInoCore()
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public V this[string key]
 | 
			
		||||
        {
 | 
			
		||||
@@ -86,7 +89,7 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
 | 
			
		||||
        public void Add(string key, V value, int cacheDurationInSeconds)
 | 
			
		||||
        {
 | 
			
		||||
            Check.ThrowNotSupportedException("ReflectionInoCache.Add(string key, V value, int cacheDurationInSeconds)");
 | 
			
		||||
            this.InstanceCache.Add<V>(key, value, cacheDurationInSeconds);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Remove(string key)
 | 
			
		||||
@@ -107,9 +110,10 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            return this.InstanceCache.Keys;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public V GetOrCreate(string cacheKey, Func<V> create)
 | 
			
		||||
        public V GetOrCreate(string cacheKey, Func<V> create, int expire = 3600)
 | 
			
		||||
        {
 | 
			
		||||
            return InstanceCache.GetOrAdd<V>(cacheKey, (a) => create());
 | 
			
		||||
            return InstanceCache.GetOrAdd<V>(cacheKey, (a) =>
 | 
			
		||||
            create(), expire);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public static class ReflectionInoHelper
 | 
			
		||||
 
 | 
			
		||||
@@ -30,10 +30,9 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
        #region Deleteable
 | 
			
		||||
        DeleteMethodInfo DeleteableByObject(object singleEntityObjectOrListObject);
 | 
			
		||||
        IDeleteable<T> Deleteable<T>() where T : class, new();
 | 
			
		||||
        IDeleteable<T> Deleteable<T>(dynamic primaryKeyValue) where T : class, new();
 | 
			
		||||
        IDeleteable<T> Deleteable<T>(dynamic[] primaryKeyValues) where T : class, new();
 | 
			
		||||
        IDeleteable<T> DeleteableById<T>(dynamic primaryKeyValue) where T : class, new();
 | 
			
		||||
        IDeleteable<T> Deleteable<T>(Expression<Func<T, bool>> expression) where T : class, new();
 | 
			
		||||
        IDeleteable<T> Deleteable<T>(List<dynamic> pkValue) where T : class, new();
 | 
			
		||||
        IDeleteable<T> DeleteableById<T>(IReadOnlyCollection<dynamic> pkValue) where T : class, new();
 | 
			
		||||
        IDeleteable<T> Deleteable<T>(IReadOnlyCollection<T> deleteObjs) where T : class, new();
 | 
			
		||||
        IDeleteable<T> DeleteableT<T>(T deleteObj) where T : class, new();
 | 
			
		||||
        #endregion
 | 
			
		||||
 
 | 
			
		||||
@@ -447,6 +447,28 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrEmpty(tableName)) return new List<DbColumnInfo>();
 | 
			
		||||
            string cacheKey = "QuestDB.GetColumnInfosByTableName." + this.SqlBuilder.GetNoTranslationColumnName(tableName).ToLower() + this.Context.CurrentConnectionConfig.ConfigId;
 | 
			
		||||
            cacheKey = GetCacheKey(cacheKey);
 | 
			
		||||
 | 
			
		||||
            if (isCache)
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
 | 
			
		||||
                {
 | 
			
		||||
                    return GetColInfo(tableName);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return GetColInfo(tableName);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private List<DbColumnInfo> GetColInfo(string tableName)
 | 
			
		||||
        {
 | 
			
		||||
            var sql = String.Format(GetColumnInfosByTableNameSql, tableName);
 | 
			
		||||
            List<DbColumnInfo> result = new List<DbColumnInfo>();
 | 
			
		||||
 
 | 
			
		||||
@@ -406,22 +406,24 @@ AND sql LIKE '%" + tableName + "%'");
 | 
			
		||||
        public override bool CreateDatabase(string databaseName, string databaseDirectory = null)
 | 
			
		||||
        {
 | 
			
		||||
            var connString = this.Context.CurrentConnectionConfig.ConnectionString;
 | 
			
		||||
            var path = Regex.Match(connString, @"[a-z,A-Z]\:\\.+\\").Value;
 | 
			
		||||
            if (path.IsNullOrEmpty())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            // 提取 Data Source=xxx(不管是绝对还是相对路径)
 | 
			
		||||
            var match = Regex.Match(connString, @"(?i)Data\s+Source\s*=\s*(.+?)(;|$)");
 | 
			
		||||
            if (match.Success)
 | 
			
		||||
            {
 | 
			
		||||
                path = Regex.Match(connString, @"\/.+\/").Value;
 | 
			
		||||
            }
 | 
			
		||||
            if (path.IsNullOrEmpty())
 | 
			
		||||
            {
 | 
			
		||||
                path = Regex.Match(connString, @"[a-z,A-Z]\:\\").Value;
 | 
			
		||||
            }
 | 
			
		||||
            if (!path.IsNullOrEmpty())
 | 
			
		||||
            {
 | 
			
		||||
                if (!FileHelper.IsExistDirectory(path))
 | 
			
		||||
                var filePath = match.Groups[1].Value.Trim(); // => ./DB/data.sqlite
 | 
			
		||||
                var folderPath = Path.GetDirectoryName(filePath); // => ./DB
 | 
			
		||||
 | 
			
		||||
                if (!folderPath.IsNullOrEmpty())
 | 
			
		||||
                {
 | 
			
		||||
                    FileHelper.CreateDirectory(path);
 | 
			
		||||
                    if (!FileHelper.IsExistDirectory(folderPath))
 | 
			
		||||
                    {
 | 
			
		||||
                        FileHelper.CreateDirectory(folderPath);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.Context.Ado.Connection.Open();
 | 
			
		||||
            this.Context.Ado.Connection.Close();
 | 
			
		||||
            return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -741,24 +741,21 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            return this.Context.Deleteable<T>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(dynamic primaryKeyValue) where T : class, new()
 | 
			
		||||
        public IDeleteable<T> DeleteableById<T>(dynamic primaryKeyValue) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return this.Context.Deleteable<T>(primaryKeyValue);
 | 
			
		||||
            return this.Context.DeleteableById<T>(primaryKeyValue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(dynamic[] primaryKeyValues) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return this.Context.Deleteable<T>(primaryKeyValues);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(Expression<Func<T, bool>> expression) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return this.Context.Deleteable(expression);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(List<dynamic> pkValue) where T : class, new()
 | 
			
		||||
        public IDeleteable<T> DeleteableById<T>(IReadOnlyCollection<dynamic> pkValue) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return this.Context.Deleteable<T>(pkValue);
 | 
			
		||||
            return this.Context.DeleteableById<T>(pkValue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(IReadOnlyCollection<T> deleteObjs) where T : class, new()
 | 
			
		||||
 
 | 
			
		||||
@@ -129,24 +129,21 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            return ScopedContext.Deleteable<T>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(dynamic primaryKeyValue) where T : class, new()
 | 
			
		||||
        public IDeleteable<T> DeleteableById<T>(dynamic primaryKeyValue) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return ScopedContext.Deleteable<T>(primaryKeyValue);
 | 
			
		||||
            return ScopedContext.DeleteableById<T>(primaryKeyValue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(dynamic[] primaryKeyValues) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return ScopedContext.Deleteable<T>(primaryKeyValues);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(Expression<Func<T, bool>> expression) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return ScopedContext.Deleteable(expression);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(List<dynamic> pkValue) where T : class, new()
 | 
			
		||||
        public IDeleteable<T> DeleteableById<T>(IReadOnlyCollection<dynamic> pkValue) where T : class, new()
 | 
			
		||||
        {
 | 
			
		||||
            return ScopedContext.Deleteable<T>(pkValue);
 | 
			
		||||
            return ScopedContext.DeleteableById<T>(pkValue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IDeleteable<T> Deleteable<T>(IReadOnlyCollection<T> deleteObjs) where T : class, new()
 | 
			
		||||
 
 | 
			
		||||
@@ -717,8 +717,32 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
        /// <returns>列信息列表</returns>
 | 
			
		||||
        public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true)
 | 
			
		||||
        {
 | 
			
		||||
            var sql = $"select * from {this.SqlBuilder.GetTranslationColumnName(tableName)} where 1=2 ";
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrEmpty(tableName)) return new List<DbColumnInfo>();
 | 
			
		||||
            string cacheKey = "TDengine.GetColumnInfosByTableName." + this.SqlBuilder.GetNoTranslationColumnName(tableName).ToLower() + this.Context.CurrentConnectionConfig.ConfigId;
 | 
			
		||||
            cacheKey = GetCacheKey(cacheKey);
 | 
			
		||||
 | 
			
		||||
            if (isCache)
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
 | 
			
		||||
                     {
 | 
			
		||||
                         return GetColInfo(tableName);
 | 
			
		||||
                     });
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return GetColInfo(tableName);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private List<DbColumnInfo> GetColInfo(string tableName)
 | 
			
		||||
        {
 | 
			
		||||
            List<DbColumnInfo> result = new List<DbColumnInfo>();
 | 
			
		||||
 | 
			
		||||
            var sql = $"select * from {this.SqlBuilder.GetTranslationColumnName(tableName)} where 1=2 ";
 | 
			
		||||
            DataTable dt = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<GenerateDocumentationFile>True</GenerateDocumentationFile>
 | 
			
		||||
		
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
 | 
			
		||||
@@ -22,20 +23,21 @@
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="SqlSugarCore.Dm" Version="8.8.0" />
 | 
			
		||||
		<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.613" />
 | 
			
		||||
		<PackageReference Include="SqlSugarCore.Dm" Version="8.8.1" />
 | 
			
		||||
		<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.821" />
 | 
			
		||||
		<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.20" />
 | 
			
		||||
		<!--<PackageReference Include="Microsoft.Data.Sqlite" Version="$(NET9Version)" />-->
 | 
			
		||||
		<PackageReference Include="MySqlConnector" Version="2.4.0" />
 | 
			
		||||
		<PackageReference Include="Npgsql" Version="9.0.3" />
 | 
			
		||||
		<PackageReference Include="CsvHelper" Version="33.1.0" />
 | 
			
		||||
		<PackageReference Include="TDengine.Connector" Version="3.1.7" />
 | 
			
		||||
		<PackageReference Include="TDengine.Connector" Version="3.1.8" />
 | 
			
		||||
		<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />
 | 
			
		||||
		<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.20" />
 | 
			
		||||
		<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.23" />
 | 
			
		||||
		<PackageReference Include="System.Data.Common" Version="4.3.0" />
 | 
			
		||||
		<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
 | 
			
		||||
		<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
 | 
			
		||||
		<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
 | 
			
		||||
		<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
 | 
			
		||||
		<PackageReference Include="System.Formats.Asn1" Version="8.0.2" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,15 @@
 | 
			
		||||
<Project>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<PluginVersion>10.9.68</PluginVersion>
 | 
			
		||||
		<ProPluginVersion>10.9.68</ProPluginVersion>
 | 
			
		||||
		<DefaultVersion>10.9.69</DefaultVersion>
 | 
			
		||||
		<AuthenticationVersion>2.9.28</AuthenticationVersion>
 | 
			
		||||
		<SourceGeneratorVersion>10.9.25</SourceGeneratorVersion>
 | 
			
		||||
		<NET8Version>8.0.18</NET8Version>
 | 
			
		||||
		<NET9Version>9.0.7</NET9Version>
 | 
			
		||||
		<PluginVersion>10.11.6</PluginVersion>
 | 
			
		||||
		<ProPluginVersion>10.11.6</ProPluginVersion>
 | 
			
		||||
		<DefaultVersion>10.11.7</DefaultVersion>
 | 
			
		||||
		<AuthenticationVersion>10.11.2</AuthenticationVersion>
 | 
			
		||||
		<SourceGeneratorVersion>10.11.2</SourceGeneratorVersion>
 | 
			
		||||
		<NET8Version>8.0.19</NET8Version>
 | 
			
		||||
		<NET9Version>9.0.8</NET9Version>
 | 
			
		||||
		<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
 | 
			
		||||
		<IsTrimmable>false</IsTrimmable>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
@@ -52,7 +53,7 @@
 | 
			
		||||
		<EmbedAllSources>True</EmbedAllSources>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="Roslynator.Analyzers" Version="4.13.1">
 | 
			
		||||
		<PackageReference Include="Roslynator.Analyzers" Version="4.14.0">
 | 
			
		||||
			<PrivateAssets>all</PrivateAssets>
 | 
			
		||||
			<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
			
		||||
		</PackageReference>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3088
									
								
								src/Drivers/ThingsGateway.AllenBradley.deps.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3088
									
								
								src/Drivers/ThingsGateway.AllenBradley.deps.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/Drivers/ThingsGateway.AllenBradley.dll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/Drivers/ThingsGateway.AllenBradley.dll
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
<Project>
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net462;netstandard2.0;net6.0;</TargetFrameworks>
 | 
			
		||||
		<TargetFrameworks>net462;netstandard2.0;net6.0;net8.0</TargetFrameworks>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,11 @@
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>netstandard2.0;</TargetFrameworks>
 | 
			
		||||
		
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="CS-Script" Version="4.9.7" />
 | 
			
		||||
		<PackageReference Include="CS-Script" Version="4.11.0" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@ public partial class ChannelComponent : ComponentBase
 | 
			
		||||
                await Channel.SetupAsync(config);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await Channel.ConnectAsync(Channel.ChannelOptions.ConnectTimeout, default);
 | 
			
		||||
            await Channel.ConnectAsync(default);
 | 
			
		||||
 | 
			
		||||
            if (OnConnectClick.HasDelegate)
 | 
			
		||||
                await OnConnectClick.InvokeAsync(Channel);
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
            <EditorItem @bind-Field=Model.EncodingName>
 | 
			
		||||
                <EditTemplate Context="value">
 | 
			
		||||
                    <div class="col-12 col-sm-4">
 | 
			
		||||
                        <Select @bind-Value=value.EncodingName Items="EncodingItems" />
 | 
			
		||||
                        <Select @bind-Value=value.EncodingName Items="EncodingItems" IsClearable/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </EditTemplate>
 | 
			
		||||
            </EditorItem>
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,6 @@ public partial class ConverterConfigComponent : ComponentBase
 | 
			
		||||
    protected override void OnInitialized()
 | 
			
		||||
    {
 | 
			
		||||
        BoolItems = LocalizerUtil.GetBoolItems(Model.GetType(), nameof(Model.VariableStringLength), true);
 | 
			
		||||
        EncodingItems = new List<SelectedItem>() { new SelectedItem("", "none") }.Concat(Encoding.GetEncodings().Select(a => new SelectedItem(a.CodePage.ToString(), a.DisplayName))).ToList();
 | 
			
		||||
        EncodingItems = Encoding.GetEncodings().Select(a => new SelectedItem(a.CodePage.ToString(), a.DisplayName)).ToList();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user