Przeglądaj źródła

Merge branch 'feature/signatureAuth' into next

许俊杰 2 lat temu
rodzic
commit
8208ca55b7
42 zmienionych plików z 2704 dodań i 3 usunięć
  1. 33 0
      Admin.NET/Admin.NET.Application/OpenApi/DemoOpenApi.cs
  2. 10 0
      Admin.NET/Admin.NET.Core/Const/CacheConst.cs
  3. 54 0
      Admin.NET/Admin.NET.Core/Entity/SysOpenAccess.cs
  4. 6 0
      Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs
  5. 6 0
      Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs
  6. 41 0
      Admin.NET/Admin.NET.Core/Service/Auth/Dto/OpenAccessInput.cs
  7. 23 0
      Admin.NET/Admin.NET.Core/Service/Auth/Dto/OpenAccessOutput.cs
  8. 173 0
      Admin.NET/Admin.NET.Core/Service/Auth/SysOpenAccessService.cs
  9. 8 0
      Admin.NET/Admin.NET.Core/Service/Tenant/Dto/TenantInput.cs
  10. 11 0
      Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs
  11. 31 0
      Admin.NET/Admin.NET.Core/SignatureAuthentication/GetAccessSecretContext.cs
  12. 26 0
      Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationDefaults.cs
  13. 58 0
      Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationEvent.cs
  14. 39 0
      Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationExtensions.cs
  15. 164 0
      Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationHandler.cs
  16. 32 0
      Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationOptions.cs
  17. 37 0
      Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureChallengeContext.cs
  18. 36 0
      Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureValidatedContext.cs
  19. 5 1
      Admin.NET/Admin.NET.Core/Util/AdminResultProvider.cs
  20. 3 0
      Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs
  21. 7 2
      Admin.NET/Admin.NET.Web.Core/Startup.cs
  22. 1 0
      Web/src/api-services/api.ts
  23. 75 0
      Web/src/api-services/apis/sys-dict-type-api.ts
  24. 466 0
      Web/src/api-services/apis/sys-open-access-api.ts
  25. 85 0
      Web/src/api-services/apis/sys-tenant-api.ts
  26. 94 0
      Web/src/api-services/models/add-open-access-input.ts
  27. 57 0
      Web/src/api-services/models/admin-result-list-sys-user.ts
  28. 57 0
      Web/src/api-services/models/admin-result-sql-sugar-paged-list-open-access-output.ts
  29. 57 0
      Web/src/api-services/models/admin-result-sql-sugar-paged-list-sys-open-access.ts
  30. 26 0
      Web/src/api-services/models/delete-open-access-input.ts
  31. 10 0
      Web/src/api-services/models/index.ts
  32. 56 0
      Web/src/api-services/models/open-access-input.ts
  33. 106 0
      Web/src/api-services/models/open-access-output.ts
  34. 63 0
      Web/src/api-services/models/sql-sugar-paged-list-open-access-output.ts
  35. 63 0
      Web/src/api-services/models/sql-sugar-paged-list-sys-open-access.ts
  36. 81 0
      Web/src/api-services/models/sys-open-access.ts
  37. 119 0
      Web/src/api-services/models/sys-tenant.ts
  38. 26 0
      Web/src/api-services/models/tenant-id-input.ts
  39. 94 0
      Web/src/api-services/models/update-open-access-input.ts
  40. 123 0
      Web/src/views/system/openAccess/component/editOpenAccess.vue
  41. 99 0
      Web/src/views/system/openAccess/component/helpView.vue
  42. 143 0
      Web/src/views/system/openAccess/index.vue

+ 33 - 0
Admin.NET/Admin.NET.Application/OpenApi/DemoOpenApi.cs

@@ -0,0 +1,33 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Microsoft.AspNetCore.Authorization;
+
+namespace Admin.NET.Application;
+
+///// <summary>
+///// 示例开放接口
+///// </summary>
+//[ApiDescriptionSettings("开放接口", Name = "Demo", Order = 100)]
+//[Authorize(AuthenticationSchemes = SignatureAuthenticationDefaults.AuthenticationScheme)]
+//public class DemoOpenApi : IDynamicApiController
+//{
+//    private readonly UserManager _userManager;
+
+//    public DemoOpenApi(UserManager userManager)
+//    {
+//        _userManager = userManager;
+//    }
+
+//    [HttpGet("helloWord")]
+//    public Task<string> HelloWord()
+//    {
+//        return Task.FromResult($"Hello word. {_userManager.Account}");
+//    }
+//}

+ 10 - 0
Admin.NET/Admin.NET.Core/Const/CacheConst.cs

@@ -71,4 +71,14 @@ public class CacheConst
     /// SqlSugar二级缓存
     /// </summary>
     public const string SqlSugar = "sys_sqlSugar:";
+
+    /// <summary>
+    /// 开放接口身份缓存
+    /// </summary>
+    public const string KeyOpenAccess = "sys_open_access:";
+
+    /// <summary>
+    /// 开放接口身份随机数缓存
+    /// </summary>
+    public const string KeyOpenAccessNonce = "sys_open_access_nonce:";
 }

+ 54 - 0
Admin.NET/Admin.NET.Core/Entity/SysOpenAccess.cs

@@ -0,0 +1,54 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 开放接口身份表
+/// </summary>
+[SugarTable(null, "开放接口身份表")]
+[SysTable]
+public class SysOpenAccess : EntityBase
+{
+    /// <summary>
+    /// 身份标识
+    /// </summary>
+    [SugarColumn(ColumnDescription = "身份标识", Length = 80)]
+    public virtual string AccessKey { get; set; }
+
+    /// <summary>
+    /// 密钥
+    /// </summary>
+    [SugarColumn(ColumnDescription = "密钥", Length = 255)]
+    public virtual string AccessSecret { get; set; }
+
+    /// <summary>
+    /// 绑定租户Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "绑定租户Id")]
+    public long BindTenantId { get; set; }
+
+    /// <summary>
+    /// 绑定租户
+    /// </summary>
+    [Navigate(NavigateType.OneToOne, nameof(BindTenantId))]
+    public SysTenant BindTenant { get; set; }
+
+    /// <summary>
+    /// 绑定用户Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "绑定用户Id")]
+    public virtual long BindUserId { get; set; }
+
+    /// <summary>
+    /// 绑定用户
+    /// </summary>
+    [Navigate(NavigateType.OneToOne, nameof(BindUserId))]
+    public SysUser BindUser { get; set; }
+}

+ 6 - 0
Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs

@@ -633,4 +633,10 @@ public enum ErrorCodeEnum
     /// </summary>
     [ErrorCodeItemMetadata("租户已禁用")]
     Z1003,
+
+    /// <summary>
+    /// 身份标识已存在
+    /// </summary>
+    [ErrorCodeItemMetadata("身份标识已存在")]
+    O1000,
 }

+ 6 - 0
Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs

@@ -149,6 +149,12 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             new SysMenu{ Id=1310000000414, Pid=1310000000411, Title="增加", Permission="sysPlugin:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
             new SysMenu{ Id=1310000000415, Pid=1310000000411, Title="删除", Permission="sysPlugin:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
 
+            new SysMenu{ Id=1310000000421, Pid=1310000000301, Title="开放接口身份", Path="/platform/openAccess", Name="sysOpenAccess", Component="/system/openAccess/index", Icon="ele-Link", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=210 },
+            new SysMenu{ Id=1310000000422, Pid=1310000000421, Title="查询", Permission="sysOpenAccess:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
+            new SysMenu{ Id=1310000000423, Pid=1310000000421, Title="编辑", Permission="sysOpenAccess:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
+            new SysMenu{ Id=1310000000424, Pid=1310000000421, Title="增加", Permission="sysOpenAccess:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
+            new SysMenu{ Id=1310000000425, Pid=1310000000421, Title="删除", Permission="sysOpenAccess:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
+
             new SysMenu{ Id=1310000000501, Pid=0, Title="日志管理", Path="/log", Name="log", Component="Layout", Icon="ele-DocumentCopy", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=12000 },
             new SysMenu{ Id=1310000000511, Pid=1310000000501, Title="访问日志", Path="/log/vislog", Name="sysVislog", Component="/system/log/vislog/index", Icon="ele-Document", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
             new SysMenu{ Id=1310000000512, Pid=1310000000511, Title="查询", Permission="sysVislog:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },

+ 41 - 0
Admin.NET/Admin.NET.Core/Service/Auth/Dto/OpenAccessInput.cs

@@ -0,0 +1,41 @@
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 开放接口身份输入参数
+/// </summary>
+public class OpenAccessInput : BasePageInput
+{
+    /// <summary>
+    /// 身份标识
+    /// </summary>
+    public string AccessKey { get; set; }
+}
+
+public class AddOpenAccessInput : SysOpenAccess
+{
+    /// <summary>
+    /// 身份标识
+    /// </summary>
+    [Required(ErrorMessage = "身份标识不能为空")]
+    public override string AccessKey { get; set; }
+
+    /// <summary>
+    /// 密钥
+    /// </summary>
+    [Required(ErrorMessage = "密钥不能为空")]
+    public override string AccessSecret { get; set; }
+
+    /// <summary>
+    /// 绑定用户Id
+    /// </summary>
+    [Required(ErrorMessage = "绑定用户不能为空")]
+    public override long BindUserId { get; set; }
+}
+
+public class UpdateOpenAccessInput : AddOpenAccessInput
+{
+}
+
+public class DeleteOpenAccessInput : BaseIdInput
+{
+}

+ 23 - 0
Admin.NET/Admin.NET.Core/Service/Auth/Dto/OpenAccessOutput.cs

@@ -0,0 +1,23 @@
+// 麻省理工学院许可证
+// 
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+// 
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+// 
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+namespace Admin.NET.Core.Service;
+
+public class OpenAccessOutput : SysOpenAccess
+{
+    /// <summary>
+    /// 绑定用户账号
+    /// </summary>
+    public string BindUserAccount { get; set; }
+
+    /// <summary>
+    /// 绑定租户名称
+    /// </summary>
+    public string BindTenantName { get; set; }
+}

+ 173 - 0
Admin.NET/Admin.NET.Core/Service/Auth/SysOpenAccessService.cs

@@ -0,0 +1,173 @@
+using Microsoft.VisualBasic;
+using System.Security.Claims;
+
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 开放接口身份服务
+/// </summary>
+[ApiDescriptionSettings(Order = 244)]
+public class SysOpenAccessService : IDynamicApiController, ITransient
+{
+    private readonly SqlSugarRepository<SysOpenAccess> _sysOpenAccessRep;
+    private readonly SqlSugarRepository<SysUser> _sysUserRep;
+    private readonly SysCacheService _sysCacheService;
+
+    /// <summary>
+    /// 开放接口身份服务构造函数
+    /// </summary>
+    public SysOpenAccessService(SqlSugarRepository<SysOpenAccess> sysOpenAccessRep,
+        SqlSugarRepository<SysUser> sysUserRep,
+        SysCacheService sysCacheService)
+    {
+        _sysOpenAccessRep = sysOpenAccessRep;
+        _sysUserRep = sysUserRep;
+        _sysCacheService = sysCacheService;
+    }
+
+    /// <summary>
+    /// 获取开放接口身份分页列表
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("获取开放接口身份分页列表")]
+    public async Task<SqlSugarPagedList<OpenAccessOutput>> Page(OpenAccessInput input)
+    {
+        return await _sysOpenAccessRep.AsQueryable()
+            .LeftJoin<SysUser>((o, u) => o.BindUserId == u.Id)
+            .LeftJoin<SysTenant>((o, u, t) => o.BindTenantId == t.Id)
+            .LeftJoin<SysOrg>((o, u, t, oo) => t.OrgId == oo.Id)
+            .WhereIF(!string.IsNullOrWhiteSpace(input.AccessKey?.Trim()), (o, u, t, oo) => o.AccessKey.Contains(input.AccessKey))
+            .Select((o, u, t, oo) =>
+                new OpenAccessOutput
+                {
+                    BindUserAccount = u.Account,
+                    BindTenantName = oo.Name,
+                }, true)
+            .ToPagedListAsync(input.Page, input.PageSize);
+    }
+
+    /// <summary>
+    /// 增加开放接口身份
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [ApiDescriptionSettings(Name = "Add"), HttpPost]
+    [DisplayName("增加开放接口身份")]
+    public async Task AddOpenAccess(AddOpenAccessInput input)
+    {
+        if (await _sysOpenAccessRep.AsQueryable().AnyAsync(u => u.AccessKey == input.AccessKey && u.Id != input.Id))
+            throw Oops.Oh(ErrorCodeEnum.O1000);
+
+        var openAccess = input.Adapt<SysOpenAccess>();
+        await _sysOpenAccessRep.InsertAsync(openAccess);
+    }
+
+    /// <summary>
+    /// 更新开放接口身份
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [ApiDescriptionSettings(Name = "Update"), HttpPost]
+    [DisplayName("更新开放接口身份")]
+    public async Task UpdateOpenAccess(UpdateOpenAccessInput input)
+    {
+        if (await _sysOpenAccessRep.AsQueryable().AnyAsync(u => u.AccessKey == input.AccessKey && u.Id != input.Id))
+            throw Oops.Oh(ErrorCodeEnum.O1000);
+
+        var openAccess = input.Adapt<SysOpenAccess>();
+        _sysCacheService.Remove(CacheConst.KeyOpenAccess + openAccess.AccessKey);
+
+        await _sysOpenAccessRep.UpdateAsync(openAccess);
+    }
+
+    /// <summary>
+    /// 删除开放接口身份
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [ApiDescriptionSettings(Name = "Delete"), HttpPost]
+    [DisplayName("删除开放接口身份")]
+    public async Task DeleteOpenAccess(DeleteOpenAccessInput input)
+    {
+        var openAccess = await _sysOpenAccessRep.GetFirstAsync(u => u.Id == input.Id);
+        if (openAccess != null)
+            _sysCacheService.Remove(CacheConst.KeyOpenAccess + openAccess.AccessKey);
+
+        await _sysOpenAccessRep.DeleteAsync(u => u.Id == input.Id);
+    }
+
+    /// <summary>
+    /// 创建密钥
+    /// </summary>
+    /// <returns></returns>
+    [DisplayName("创建密钥")]
+    public Task<string> CreateSecret()
+    {
+        return Task.FromResult(Convert.ToBase64String(Guid.NewGuid().ToByteArray())[..^2]);
+    }
+
+    /// <summary>
+    /// 根据 Key 获取对象
+    /// </summary>
+    /// <param name="accessKey"></param>
+    /// <returns></returns>
+    [NonAction]
+    public Task<SysOpenAccess> GetByKey([FromQuery] string accessKey)
+    {
+        return Task.FromResult(
+            _sysCacheService.GetOrAdd(CacheConst.KeyOpenAccess + accessKey, _ =>
+            {
+                return _sysOpenAccessRep.AsQueryable()
+                    .Includes(u => u.BindUser)
+                    .Includes(u => u.BindUser, p => p.SysOrg)
+                    .First(u => u.AccessKey == accessKey);
+            })
+        );
+    }
+
+    /// <summary>
+    /// Signature 身份验证事件默认实现
+    /// </summary>
+    [NonAction]
+    public static SignatureAuthenticationEvent GetSignatureAuthenticationEventImpl()
+    {
+        return new SignatureAuthenticationEvent
+        {
+            OnGetAccessSecret = context =>
+            {
+                var logger = context.HttpContext.RequestServices.GetService<ILogger<SysOpenAccessService>>();
+                try
+                {
+                    var openAccessService = context.HttpContext.RequestServices.GetService<SysOpenAccessService>();
+                    var openAccess = openAccessService.GetByKey(context.AccessKey).GetAwaiter().GetResult();
+                    return Task.FromResult(openAccess == null ? "" : openAccess.AccessSecret);
+                }
+                catch (Exception ex)
+                {
+                    logger.LogError(ex, ex.Message);
+                    return Task.FromResult("");
+                }
+            },
+            OnValidated = context =>
+            {
+                var openAccessService = context.HttpContext.RequestServices.GetService<SysOpenAccessService>();
+                var openAccess = openAccessService.GetByKey(context.AccessKey).GetAwaiter().GetResult();
+                var identity = ((ClaimsIdentity)context.Principal!.Identity!);
+
+                identity.AddClaims(new[]
+                {
+                    new Claim(ClaimConst.UserId, openAccess.BindUserId + ""),
+                    new Claim(ClaimConst.TenantId, openAccess.BindTenantId + ""),
+                    new Claim(ClaimConst.Account, openAccess.BindUser.Account + ""),
+                    new Claim(ClaimConst.RealName, openAccess.BindUser.RealName),
+                    new Claim(ClaimConst.AccountType, ((int) openAccess.BindUser.AccountType).ToString()),
+                    new Claim(ClaimConst.OrgId, openAccess.BindUser.OrgId + ""),
+                    new Claim(ClaimConst.OrgName, openAccess.BindUser.SysOrg?.Name + ""),
+                    new Claim(ClaimConst.OrgType, openAccess.BindUser.SysOrg?.Type + ""),
+                });
+                return Task.CompletedTask;
+            }
+        };
+    }
+}

+ 8 - 0
Admin.NET/Admin.NET.Core/Service/Tenant/Dto/TenantInput.cs

@@ -59,4 +59,12 @@ public class TenantUserInput
     /// 用户Id
     /// </summary>
     public long UserId { get; set; }
+}
+
+public class TenantIdInput
+{
+    /// <summary>
+    /// 租户Id
+    /// </summary>
+    public long TenantId { get; set; }
 }

+ 11 - 0
Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs

@@ -401,4 +401,15 @@ public class SysTenantService : IDynamicApiController, ITransient
         };
         SqlSugarSetup.InitTenantDatabase(App.GetRequiredService<ISqlSugarClient>().AsTenant(), config);
     }
+
+    /// <summary>
+    /// 获取租户下的用户列表
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("获取租户下的用户列表")]
+    public async Task<List<SysUser>> UserList(TenantIdInput input)
+    {
+        return await _sysUserRep.AsQueryable().Filter(null, true).Where(u => u.TenantId == input.TenantId).ToListAsync();
+    }
 }

+ 31 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/GetAccessSecretContext.cs

@@ -0,0 +1,31 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Microsoft.AspNetCore.Authentication;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 获取 AccessKey 关联 AccessSecret 方法的上下文
+/// </summary>
+public class GetAccessSecretContext : BaseContext<SignatureAuthenticationOptions>
+{
+    public GetAccessSecretContext(
+        HttpContext context,
+        AuthenticationScheme scheme,
+        SignatureAuthenticationOptions options)
+        : base(context, scheme, options)
+    {
+    }
+
+    /// <summary>
+    /// 身份标识
+    /// </summary>
+    public string AccessKey { get; set; }
+}

+ 26 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationDefaults.cs

@@ -0,0 +1,26 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证处理程序相关的默认值
+/// </summary>
+public static class SignatureAuthenticationDefaults
+{
+    /// <summary>
+    /// SignatureAuthenticationOptions.AuthenticationScheme 使用的默认值
+    /// </summary>
+    public const string AuthenticationScheme = "Signature";
+
+    /// <summary>
+    /// 附加在在 HttpContext Item 中验证失败消息的 Key
+    /// </summary>
+    public const string AuthenticateFailMsgKey = "SignatureAuthenticateFailMsg";
+}

+ 58 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationEvent.cs

@@ -0,0 +1,58 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using System.Security.Claims;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证事件
+/// </summary>
+public class SignatureAuthenticationEvent
+{
+    public SignatureAuthenticationEvent()
+    {
+    }
+
+    /// <summary>
+    /// 获取或设置获取 AccessKey 的 AccessSecret 的逻辑处理
+    /// </summary>
+    public Func<GetAccessSecretContext, Task<string>> OnGetAccessSecret { get; set; }
+
+    /// <summary>
+    /// 获取或设置质询的逻辑处理
+    /// </summary>
+    public Func<SignatureChallengeContext, Task> OnChallenge { get; set; } = _ => Task.CompletedTask;
+
+    /// <summary>
+    /// 获取或设置已验证的逻辑处理
+    /// </summary>
+    public Func<SignatureValidatedContext, Task> OnValidated { get; set; } = _ => Task.CompletedTask;
+
+    /// <summary>
+    /// 获取 AccessKey 的 AccessSecret
+    /// </summary>
+    /// <param name="context"></param>
+    /// <returns></returns>
+    public virtual Task<string> GetAccessSecret(GetAccessSecretContext context) => OnGetAccessSecret?.Invoke(context) ?? throw new NotImplementedException($"需要提供 {nameof(OnGetAccessSecret)} 实现");
+
+    /// <summary>
+    /// 质询
+    /// </summary>
+    /// <param name="context"></param>
+    /// <returns></returns>
+    public virtual Task Challenge(SignatureChallengeContext context) => OnChallenge?.Invoke(context) ?? Task.CompletedTask;
+
+    /// <summary>
+    /// 已验证成功
+    /// </summary>
+    /// <param name="context"></param>
+    /// <returns></returns>
+    public virtual Task Validated(SignatureValidatedContext context) => OnValidated?.Invoke(context) ?? Task.CompletedTask;
+}

+ 39 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationExtensions.cs

@@ -0,0 +1,39 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Microsoft.AspNetCore.Authentication;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证扩展
+/// </summary>
+public static class SignatureAuthenticationExtensions
+{
+    /// <summary>
+    /// 注册 Signature 身份验证处理模块
+    /// </summary>
+    /// <param name="builder"></param>
+    /// <returns></returns>
+    public static AuthenticationBuilder AddSignatureAuthentication(this AuthenticationBuilder builder)
+    {
+        return builder.AddSignatureAuthentication(options => { });
+    }
+
+    /// <summary>
+    /// 注册 Signature 身份验证处理模块
+    /// </summary>
+    /// <param name="builder"></param>
+    /// <param name="options"></param>
+    /// <returns></returns>
+    public static AuthenticationBuilder AddSignatureAuthentication(this AuthenticationBuilder builder, Action<SignatureAuthenticationOptions> options)
+    {
+        return builder.AddScheme<SignatureAuthenticationOptions, SignatureAuthenticationHandler>(SignatureAuthenticationDefaults.AuthenticationScheme, options);
+    }
+}

+ 164 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationHandler.cs

@@ -0,0 +1,164 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using System.Security.Claims;
+using System.Security.Cryptography;
+using System.Text.Encodings.Web;
+using Microsoft.AspNetCore.Authentication;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证处理
+/// </summary>
+public sealed class SignatureAuthenticationHandler : AuthenticationHandler<SignatureAuthenticationOptions>
+{
+    private SysCacheService _cacheService;
+
+    public SignatureAuthenticationHandler(IOptionsMonitor<SignatureAuthenticationOptions> options,
+        ILoggerFactory logger,
+        UrlEncoder encoder,
+        ISystemClock clock,
+        SysCacheService cacheService)
+        : base(options, logger, encoder, clock)
+    {
+        _cacheService = cacheService;
+    }
+
+    private new SignatureAuthenticationEvent Events
+    {
+        get => (SignatureAuthenticationEvent)base.Events;
+        set => base.Events = value;
+    }
+
+    /// <summary>
+    /// 确保创建的 Event 类型是 DigestEvents
+    /// </summary>
+    /// <returns></returns>
+    protected override Task<object> CreateEventsAsync() => throw new NotImplementedException($"{nameof(SignatureAuthenticationOptions)}.{nameof(SignatureAuthenticationOptions.Events)} 需要提供一个实例");
+
+    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
+    {
+        var accessKey = Request.Headers["accessKey"].FirstOrDefault();
+        var timestampStr = Request.Headers["timestamp"].FirstOrDefault();//精确到秒
+        var nonce = Request.Headers["nonce"].FirstOrDefault();
+        var sign = Request.Headers["sign"].FirstOrDefault();
+
+        if (string.IsNullOrEmpty(accessKey))
+            return await AuthenticateResultFailAsync("accessKey 不能为空");
+        if (string.IsNullOrEmpty(timestampStr))
+            return await AuthenticateResultFailAsync("timestamp 不能为空");
+        if (string.IsNullOrEmpty(nonce))
+            return await AuthenticateResultFailAsync("nonce 不能为空");
+        if (string.IsNullOrEmpty(sign))
+            return await AuthenticateResultFailAsync("sign 不能为空");
+
+        //验证请求数据是否在可接受的时间内
+        if (!long.TryParse(timestampStr, out var timestamp))
+            return await AuthenticateResultFailAsync("timestamp 值不合法");
+
+        var requestDate = DateTimeUtil.ToLocalTimeDateBySeconds(timestamp);
+        if (requestDate > Clock.UtcNow.Add(Options.AllowedDateDrift).LocalDateTime || requestDate < Clock.UtcNow.Subtract(Options.AllowedDateDrift).LocalDateTime)
+            return await AuthenticateResultFailAsync("timestamp 值已超过允许的偏差范围");
+
+        //获取 accessSecret
+        var getAccessSecretContext = new GetAccessSecretContext(Context, Scheme, Options) { AccessKey = accessKey };
+        var accessSecret = await Events.GetAccessSecret(getAccessSecretContext);
+        if (string.IsNullOrEmpty(accessSecret))
+            return await AuthenticateResultFailAsync("accessKey 无效");
+
+        //校验签名
+        var appSecretByte = Encoding.UTF8.GetBytes(accessSecret);
+        string serverSign = SignData(appSecretByte, GetMessageForSign(Context));
+
+        if (serverSign != sign)
+            return await AuthenticateResultFailAsync("sign 无效的签名");
+
+        //重放检测
+        var cacheKey = $"{CacheConst.KeyOpenAccessNonce}{accessKey}|{nonce}";
+        if (_cacheService.ExistKey(cacheKey))
+            return await AuthenticateResultFailAsync("重复的请求");
+        _cacheService.Set(cacheKey, null, Options.AllowedDateDrift * 2);//缓存过期时间为偏差范围时间的2倍
+
+        //已验证成功
+        var signatureValidatedContext = new SignatureValidatedContext(Context, Scheme, Options)
+        {
+            Principal = new ClaimsPrincipal(new ClaimsIdentity(SignatureAuthenticationDefaults.AuthenticationScheme)),
+            AccessKey = accessKey
+        };
+        await Events.Validated(signatureValidatedContext);
+        // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+        if (signatureValidatedContext.Result != null)
+            return signatureValidatedContext.Result;
+
+        // ReSharper disable once HeuristicUnreachableCode
+        signatureValidatedContext.Success();
+        return signatureValidatedContext.Result;
+    }
+
+    protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
+    {
+        var authResult = await HandleAuthenticateOnceSafeAsync();
+        var challengeContext = new SignatureChallengeContext(Context, Scheme, Options, properties)
+        {
+            AuthenticateFailure = authResult.Failure,
+        };
+        await Events.Challenge(challengeContext);
+        //质询已处理
+        if (challengeContext.Handled) return;
+
+        await base.HandleChallengeAsync(properties);
+    }
+
+    /// <summary>
+    /// 获取用于签名的消息
+    /// </summary>
+    /// <returns></returns>
+    private static string GetMessageForSign(HttpContext context)
+    {
+        var method = context.Request.Method;//请求方法(大写)
+        var url = context.Request.Path;//请求 url,去除协议、域名、参数,以 / 开头
+        var accessKey = context.Request.Headers["accessKey"].FirstOrDefault();//身份标识
+        var timestamp = context.Request.Headers["timestamp"].FirstOrDefault();//时间戳,精确到秒
+        var nonce = context.Request.Headers["nonce"].FirstOrDefault();//唯一随机数
+
+        return $"{method}&{url}&{accessKey}&{timestamp}&{nonce}";
+    }
+
+    /// <summary>
+    /// 对数据进行签名
+    /// </summary>
+    /// <param name="secret"></param>
+    /// <param name="data"></param>
+    /// <returns></returns>
+    private static string SignData(byte[] secret, string data)
+    {
+        if (secret == null)
+            throw new ArgumentNullException(nameof(secret));
+
+        if (data == null)
+            throw new ArgumentNullException(nameof(data));
+
+        using HMAC hmac = new HMACSHA256();
+        hmac.Key = secret;
+        return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(data)));
+    }
+
+    /// <summary>
+    /// 返回验证失败结果,并在 Items 中增加 <see cref="SignatureAuthenticationDefaults.AuthenticateFailMsgKey"/>,记录身份验证失败消息
+    /// </summary>
+    /// <param name="message"></param>
+    /// <returns></returns>
+    private Task<AuthenticateResult> AuthenticateResultFailAsync(string message)
+    {
+        //写入身份验证失败消息
+        Context.Items[SignatureAuthenticationDefaults.AuthenticateFailMsgKey] = message;
+        return Task.FromResult(AuthenticateResult.Fail(message));
+    }
+}

+ 32 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationOptions.cs

@@ -0,0 +1,32 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Microsoft.AspNetCore.Authentication;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证选项
+/// </summary>
+public class SignatureAuthenticationOptions : AuthenticationSchemeOptions
+{
+    /// <summary>
+    /// 请求时间允许的偏差范围
+    /// </summary>
+    public TimeSpan AllowedDateDrift { get; set; } = TimeSpan.FromMinutes(5);
+
+    /// <summary>
+    /// Signature 身份验证事件
+    /// </summary>
+    public new SignatureAuthenticationEvent Events
+    {
+        get => (SignatureAuthenticationEvent)base.Events;
+        set => base.Events = value;
+    }
+}

+ 37 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureChallengeContext.cs

@@ -0,0 +1,37 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Microsoft.AspNetCore.Authentication;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证质询上下文
+/// </summary>
+public class SignatureChallengeContext : PropertiesContext<SignatureAuthenticationOptions>
+{
+    public SignatureChallengeContext(
+        HttpContext context,
+        AuthenticationScheme scheme,
+        SignatureAuthenticationOptions options,
+        AuthenticationProperties properties)
+        : base(context, scheme, options, properties)
+    {
+    }
+
+    /// <summary>
+    /// 在认证期间出现的异常
+    /// </summary>
+    public Exception AuthenticateFailure { get; set; }
+
+    /// <summary>
+    /// 指定是否已被处理,如果已处理,则跳过默认认证逻辑
+    /// </summary>
+    public bool Handled { get; private set; }
+}

+ 36 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureValidatedContext.cs

@@ -0,0 +1,36 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Microsoft.AspNetCore.Authentication;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证已验证上下文
+/// </summary>
+public class SignatureValidatedContext : ResultContext<SignatureAuthenticationOptions>
+{
+    public SignatureValidatedContext(
+        HttpContext context,
+        AuthenticationScheme scheme,
+        SignatureAuthenticationOptions options)
+        : base(context, scheme, options)
+    {
+    }
+
+    /// <summary>
+    /// 身份标识
+    /// </summary>
+    public string AccessKey { get; set; }
+
+    /// <summary>
+    /// 密钥
+    /// </summary>
+    public string AccessSecret { get; set; }
+}

+ 5 - 1
Admin.NET/Admin.NET.Core/Util/AdminResultProvider.cs

@@ -64,7 +64,11 @@ public class AdminResultProvider : IUnifyResultProvider
         {
             // 处理 401 状态码
             case StatusCodes.Status401Unauthorized:
-                await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "401 登录已过期,请重新登录"),
+                var msg = "401 登录已过期,请重新登录";
+                //20231005 如果存在身份验证失败消息,则返回消息内容
+                if (context.Items.TryGetValue(SignatureAuthenticationDefaults.AuthenticateFailMsgKey, out var authFailMsg))
+                    msg = authFailMsg + "";
+                await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: msg),
                     App.GetOptions<JsonOptions>()?.JsonSerializerOptions);
                 break;
             // 处理 403 状态码

+ 3 - 0
Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs

@@ -44,6 +44,9 @@ namespace Admin.NET.Web.Core
                 DefaultHttpContext currentHttpContext = context.GetCurrentHttpContext();
                 if (currentHttpContext == null)
                     return;
+                // 跳过由于 SignatureAuthentication 引发的失败
+                if (currentHttpContext.Items.ContainsKey(SignatureAuthenticationDefaults.AuthenticateFailMsgKey))
+                    return;
                 currentHttpContext.SignoutToSwagger();
             }
         }

+ 7 - 2
Admin.NET/Admin.NET.Web.Core/Startup.cs

@@ -1,4 +1,4 @@
-// 麻省理工学院许可证
+// 麻省理工学院许可证
 //
 // 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
 //
@@ -39,7 +39,12 @@ public class Startup : AppStartup
         // SqlSugar
         services.AddSqlSugar();
         // JWT
-        services.AddJwt<JwtHandler>(enableGlobalAuthorize: true);
+        services.AddJwt<JwtHandler>(enableGlobalAuthorize: true)
+            // 添加 Signature 身份验证
+            .AddSignatureAuthentication(options =>
+            {
+                options.Events = SysOpenAccessService.GetSignatureAuthenticationEventImpl();
+            });
         // 允许跨域
         services.AddCorsAccessor();
         // 远程请求

+ 1 - 0
Web/src/api-services/api.ts

@@ -32,6 +32,7 @@ export * from './apis/sys-message-api';
 export * from './apis/sys-notice-api';
 export * from './apis/sys-oauth-api';
 export * from './apis/sys-online-user-api';
+export * from './apis/sys-open-access-api';
 export * from './apis/sys-org-api';
 export * from './apis/sys-plugin-api';
 export * from './apis/sys-pos-api';

+ 75 - 0
Web/src/api-services/apis/sys-dict-type-api.ts

@@ -280,6 +280,49 @@ export const SysDictTypeApiAxiosParamCreator = function (configuration?: Configu
                 options: localVarRequestOptions,
             };
         },
+        /**
+         * 
+         * @summary 获取所有字典集合
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        apiSysDictTypeGetAllDictGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/api/sysDictType/getAllDict`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+            const localVarRequestOptions :AxiosRequestConfig = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication Bearer required
+            // http bearer authentication required
+            if (configuration && configuration.accessToken) {
+                const accessToken = typeof configuration.accessToken === 'function'
+                    ? await configuration.accessToken()
+                    : await configuration.accessToken;
+                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
+            }
+
+            const query = new URLSearchParams(localVarUrlObj.search);
+            for (const key in localVarQueryParameter) {
+                query.set(key, localVarQueryParameter[key]);
+            }
+            for (const key in options.params) {
+                query.set(key, options.params[key]);
+            }
+            localVarUrlObj.search = (new URLSearchParams(query)).toString();
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
+                options: localVarRequestOptions,
+            };
+        },
         /**
          * 
          * @summary 获取字典类型列表
@@ -546,6 +589,19 @@ export const SysDictTypeApiFp = function(configuration?: Configuration) {
                 return axios.request(axiosRequestArgs);
             };
         },
+        /**
+         * 
+         * @summary 获取所有字典集合
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysDictTypeGetAllDictGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultListSysDictType>>> {
+            const localVarAxiosArgs = await SysDictTypeApiAxiosParamCreator(configuration).apiSysDictTypeGetAllDictGet(options);
+            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+                return axios.request(axiosRequestArgs);
+            };
+        },
         /**
          * 
          * @summary 获取字典类型列表
@@ -660,6 +716,15 @@ export const SysDictTypeApiFactory = function (configuration?: Configuration, ba
         async apiSysDictTypeDetailGet(id: number, status?: StatusEnum, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultSysDictType>> {
             return SysDictTypeApiFp(configuration).apiSysDictTypeDetailGet(id, status, options).then((request) => request(axios, basePath));
         },
+        /**
+         * 
+         * @summary 获取所有字典集合
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysDictTypeGetAllDictGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultListSysDictType>> {
+            return SysDictTypeApiFp(configuration).apiSysDictTypeGetAllDictGet(options).then((request) => request(axios, basePath));
+        },
         /**
          * 
          * @summary 获取字典类型列表
@@ -764,6 +829,16 @@ export class SysDictTypeApi extends BaseAPI {
     public async apiSysDictTypeDetailGet(id: number, status?: StatusEnum, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultSysDictType>> {
         return SysDictTypeApiFp(this.configuration).apiSysDictTypeDetailGet(id, status, options).then((request) => request(this.axios, this.basePath));
     }
+    /**
+     * 
+     * @summary 获取所有字典集合
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof SysDictTypeApi
+     */
+    public async apiSysDictTypeGetAllDictGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultListSysDictType>> {
+        return SysDictTypeApiFp(this.configuration).apiSysDictTypeGetAllDictGet(options).then((request) => request(this.axios, this.basePath));
+    }
     /**
      * 
      * @summary 获取字典类型列表

+ 466 - 0
Web/src/api-services/apis/sys-open-access-api.ts

@@ -0,0 +1,466 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+import globalAxios, { AxiosResponse, AxiosInstance, AxiosRequestConfig } from 'axios';
+import { Configuration } from '../configuration';
+// Some imports not used depending on template conditions
+// @ts-ignore
+import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from '../base';
+import { AddOpenAccessInput } from '../models';
+import { AdminResultSqlSugarPagedListOpenAccessOutput } from '../models';
+import { AdminResultString } from '../models';
+import { DeleteOpenAccessInput } from '../models';
+import { OpenAccessInput } from '../models';
+import { UpdateOpenAccessInput } from '../models';
+/**
+ * SysOpenAccessApi - axios parameter creator
+ * @export
+ */
+export const SysOpenAccessApiAxiosParamCreator = function (configuration?: Configuration) {
+    return {
+        /**
+         * 
+         * @summary 增加开放接口身份
+         * @param {AddOpenAccessInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        apiSysOpenAccessAddPost: async (body?: AddOpenAccessInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/api/sysOpenAccess/add`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication Bearer required
+            // http bearer authentication required
+            if (configuration && configuration.accessToken) {
+                const accessToken = typeof configuration.accessToken === 'function'
+                    ? await configuration.accessToken()
+                    : await configuration.accessToken;
+                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
+            }
+
+            localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
+
+            const query = new URLSearchParams(localVarUrlObj.search);
+            for (const key in localVarQueryParameter) {
+                query.set(key, localVarQueryParameter[key]);
+            }
+            for (const key in options.params) {
+                query.set(key, options.params[key]);
+            }
+            localVarUrlObj.search = (new URLSearchParams(query)).toString();
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
+            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
+
+            return {
+                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @summary 删除开放接口身份
+         * @param {DeleteOpenAccessInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        apiSysOpenAccessDeletePost: async (body?: DeleteOpenAccessInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/api/sysOpenAccess/delete`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication Bearer required
+            // http bearer authentication required
+            if (configuration && configuration.accessToken) {
+                const accessToken = typeof configuration.accessToken === 'function'
+                    ? await configuration.accessToken()
+                    : await configuration.accessToken;
+                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
+            }
+
+            localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
+
+            const query = new URLSearchParams(localVarUrlObj.search);
+            for (const key in localVarQueryParameter) {
+                query.set(key, localVarQueryParameter[key]);
+            }
+            for (const key in options.params) {
+                query.set(key, options.params[key]);
+            }
+            localVarUrlObj.search = (new URLSearchParams(query)).toString();
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
+            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
+
+            return {
+                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @summary 获取开放接口身份分页列表
+         * @param {OpenAccessInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        apiSysOpenAccessPagePost: async (body?: OpenAccessInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/api/sysOpenAccess/page`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication Bearer required
+            // http bearer authentication required
+            if (configuration && configuration.accessToken) {
+                const accessToken = typeof configuration.accessToken === 'function'
+                    ? await configuration.accessToken()
+                    : await configuration.accessToken;
+                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
+            }
+
+            localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
+
+            const query = new URLSearchParams(localVarUrlObj.search);
+            for (const key in localVarQueryParameter) {
+                query.set(key, localVarQueryParameter[key]);
+            }
+            for (const key in options.params) {
+                query.set(key, options.params[key]);
+            }
+            localVarUrlObj.search = (new URLSearchParams(query)).toString();
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
+            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
+
+            return {
+                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @summary 创建密钥
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        apiSysOpenAccessSecretPost: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/api/sysOpenAccess/secret`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication Bearer required
+            // http bearer authentication required
+            if (configuration && configuration.accessToken) {
+                const accessToken = typeof configuration.accessToken === 'function'
+                    ? await configuration.accessToken()
+                    : await configuration.accessToken;
+                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
+            }
+
+            const query = new URLSearchParams(localVarUrlObj.search);
+            for (const key in localVarQueryParameter) {
+                query.set(key, localVarQueryParameter[key]);
+            }
+            for (const key in options.params) {
+                query.set(key, options.params[key]);
+            }
+            localVarUrlObj.search = (new URLSearchParams(query)).toString();
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @summary 更新开放接口身份
+         * @param {UpdateOpenAccessInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        apiSysOpenAccessUpdatePost: async (body?: UpdateOpenAccessInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/api/sysOpenAccess/update`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication Bearer required
+            // http bearer authentication required
+            if (configuration && configuration.accessToken) {
+                const accessToken = typeof configuration.accessToken === 'function'
+                    ? await configuration.accessToken()
+                    : await configuration.accessToken;
+                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
+            }
+
+            localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
+
+            const query = new URLSearchParams(localVarUrlObj.search);
+            for (const key in localVarQueryParameter) {
+                query.set(key, localVarQueryParameter[key]);
+            }
+            for (const key in options.params) {
+                query.set(key, options.params[key]);
+            }
+            localVarUrlObj.search = (new URLSearchParams(query)).toString();
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
+            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
+
+            return {
+                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
+                options: localVarRequestOptions,
+            };
+        },
+    }
+};
+
+/**
+ * SysOpenAccessApi - functional programming interface
+ * @export
+ */
+export const SysOpenAccessApiFp = function(configuration?: Configuration) {
+    return {
+        /**
+         * 
+         * @summary 增加开放接口身份
+         * @param {AddOpenAccessInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysOpenAccessAddPost(body?: AddOpenAccessInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
+            const localVarAxiosArgs = await SysOpenAccessApiAxiosParamCreator(configuration).apiSysOpenAccessAddPost(body, options);
+            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+                return axios.request(axiosRequestArgs);
+            };
+        },
+        /**
+         * 
+         * @summary 删除开放接口身份
+         * @param {DeleteOpenAccessInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysOpenAccessDeletePost(body?: DeleteOpenAccessInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
+            const localVarAxiosArgs = await SysOpenAccessApiAxiosParamCreator(configuration).apiSysOpenAccessDeletePost(body, options);
+            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+                return axios.request(axiosRequestArgs);
+            };
+        },
+        /**
+         * 
+         * @summary 获取开放接口身份分页列表
+         * @param {OpenAccessInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysOpenAccessPagePost(body?: OpenAccessInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultSqlSugarPagedListOpenAccessOutput>>> {
+            const localVarAxiosArgs = await SysOpenAccessApiAxiosParamCreator(configuration).apiSysOpenAccessPagePost(body, options);
+            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+                return axios.request(axiosRequestArgs);
+            };
+        },
+        /**
+         * 
+         * @summary 创建密钥
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysOpenAccessSecretPost(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultString>>> {
+            const localVarAxiosArgs = await SysOpenAccessApiAxiosParamCreator(configuration).apiSysOpenAccessSecretPost(options);
+            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+                return axios.request(axiosRequestArgs);
+            };
+        },
+        /**
+         * 
+         * @summary 更新开放接口身份
+         * @param {UpdateOpenAccessInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysOpenAccessUpdatePost(body?: UpdateOpenAccessInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
+            const localVarAxiosArgs = await SysOpenAccessApiAxiosParamCreator(configuration).apiSysOpenAccessUpdatePost(body, options);
+            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+                return axios.request(axiosRequestArgs);
+            };
+        },
+    }
+};
+
+/**
+ * SysOpenAccessApi - factory interface
+ * @export
+ */
+export const SysOpenAccessApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
+    return {
+        /**
+         * 
+         * @summary 增加开放接口身份
+         * @param {AddOpenAccessInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysOpenAccessAddPost(body?: AddOpenAccessInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
+            return SysOpenAccessApiFp(configuration).apiSysOpenAccessAddPost(body, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @summary 删除开放接口身份
+         * @param {DeleteOpenAccessInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysOpenAccessDeletePost(body?: DeleteOpenAccessInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
+            return SysOpenAccessApiFp(configuration).apiSysOpenAccessDeletePost(body, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @summary 获取开放接口身份分页列表
+         * @param {OpenAccessInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysOpenAccessPagePost(body?: OpenAccessInput, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultSqlSugarPagedListOpenAccessOutput>> {
+            return SysOpenAccessApiFp(configuration).apiSysOpenAccessPagePost(body, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @summary 创建密钥
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysOpenAccessSecretPost(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultString>> {
+            return SysOpenAccessApiFp(configuration).apiSysOpenAccessSecretPost(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @summary 更新开放接口身份
+         * @param {UpdateOpenAccessInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysOpenAccessUpdatePost(body?: UpdateOpenAccessInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
+            return SysOpenAccessApiFp(configuration).apiSysOpenAccessUpdatePost(body, options).then((request) => request(axios, basePath));
+        },
+    };
+};
+
+/**
+ * SysOpenAccessApi - object-oriented interface
+ * @export
+ * @class SysOpenAccessApi
+ * @extends {BaseAPI}
+ */
+export class SysOpenAccessApi extends BaseAPI {
+    /**
+     * 
+     * @summary 增加开放接口身份
+     * @param {AddOpenAccessInput} [body] 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof SysOpenAccessApi
+     */
+    public async apiSysOpenAccessAddPost(body?: AddOpenAccessInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
+        return SysOpenAccessApiFp(this.configuration).apiSysOpenAccessAddPost(body, options).then((request) => request(this.axios, this.basePath));
+    }
+    /**
+     * 
+     * @summary 删除开放接口身份
+     * @param {DeleteOpenAccessInput} [body] 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof SysOpenAccessApi
+     */
+    public async apiSysOpenAccessDeletePost(body?: DeleteOpenAccessInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
+        return SysOpenAccessApiFp(this.configuration).apiSysOpenAccessDeletePost(body, options).then((request) => request(this.axios, this.basePath));
+    }
+    /**
+     * 
+     * @summary 获取开放接口身份分页列表
+     * @param {OpenAccessInput} [body] 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof SysOpenAccessApi
+     */
+    public async apiSysOpenAccessPagePost(body?: OpenAccessInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultSqlSugarPagedListOpenAccessOutput>> {
+        return SysOpenAccessApiFp(this.configuration).apiSysOpenAccessPagePost(body, options).then((request) => request(this.axios, this.basePath));
+    }
+    /**
+     * 
+     * @summary 创建密钥
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof SysOpenAccessApi
+     */
+    public async apiSysOpenAccessSecretPost(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultString>> {
+        return SysOpenAccessApiFp(this.configuration).apiSysOpenAccessSecretPost(options).then((request) => request(this.axios, this.basePath));
+    }
+    /**
+     * 
+     * @summary 更新开放接口身份
+     * @param {UpdateOpenAccessInput} [body] 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof SysOpenAccessApi
+     */
+    public async apiSysOpenAccessUpdatePost(body?: UpdateOpenAccessInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
+        return SysOpenAccessApiFp(this.configuration).apiSysOpenAccessUpdatePost(body, options).then((request) => request(this.axios, this.basePath));
+    }
+}

+ 85 - 0
Web/src/api-services/apis/sys-tenant-api.ts

@@ -19,11 +19,13 @@ import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } fr
 import { AddTenantInput } from '../models';
 import { AdminResultInt32 } from '../models';
 import { AdminResultListInt64 } from '../models';
+import { AdminResultListSysUser } from '../models';
 import { AdminResultSqlSugarPagedListTenantOutput } from '../models';
 import { AdminResultString } from '../models';
 import { DeleteTenantInput } from '../models';
 import { PageTenantInput } from '../models';
 import { RoleMenuInput } from '../models';
+import { TenantIdInput } from '../models';
 import { TenantInput } from '../models';
 import { TenantUserInput } from '../models';
 import { UpdateTenantInput } from '../models';
@@ -460,6 +462,54 @@ export const SysTenantApiAxiosParamCreator = function (configuration?: Configura
             const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
             localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
 
+            return {
+                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @summary 获取租户下的用户列表
+         * @param {TenantIdInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        apiSysTenantUserListPost: async (body?: TenantIdInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/api/sysTenant/userList`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication Bearer required
+            // http bearer authentication required
+            if (configuration && configuration.accessToken) {
+                const accessToken = typeof configuration.accessToken === 'function'
+                    ? await configuration.accessToken()
+                    : await configuration.accessToken;
+                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
+            }
+
+            localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
+
+            const query = new URLSearchParams(localVarUrlObj.search);
+            for (const key in localVarQueryParameter) {
+                query.set(key, localVarQueryParameter[key]);
+            }
+            for (const key in options.params) {
+                query.set(key, options.params[key]);
+            }
+            localVarUrlObj.search = (new URLSearchParams(query)).toString();
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
+            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
+
             return {
                 url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
                 options: localVarRequestOptions,
@@ -600,6 +650,20 @@ export const SysTenantApiFp = function(configuration?: Configuration) {
                 return axios.request(axiosRequestArgs);
             };
         },
+        /**
+         * 
+         * @summary 获取租户下的用户列表
+         * @param {TenantIdInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysTenantUserListPost(body?: TenantIdInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultListSysUser>>> {
+            const localVarAxiosArgs = await SysTenantApiAxiosParamCreator(configuration).apiSysTenantUserListPost(body, options);
+            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+                return axios.request(axiosRequestArgs);
+            };
+        },
     }
 };
 
@@ -699,6 +763,16 @@ export const SysTenantApiFactory = function (configuration?: Configuration, base
         async apiSysTenantUpdatePost(body?: UpdateTenantInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
             return SysTenantApiFp(configuration).apiSysTenantUpdatePost(body, options).then((request) => request(axios, basePath));
         },
+        /**
+         * 
+         * @summary 获取租户下的用户列表
+         * @param {TenantIdInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysTenantUserListPost(body?: TenantIdInput, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultListSysUser>> {
+            return SysTenantApiFp(configuration).apiSysTenantUserListPost(body, options).then((request) => request(axios, basePath));
+        },
     };
 };
 
@@ -808,4 +882,15 @@ export class SysTenantApi extends BaseAPI {
     public async apiSysTenantUpdatePost(body?: UpdateTenantInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
         return SysTenantApiFp(this.configuration).apiSysTenantUpdatePost(body, options).then((request) => request(this.axios, this.basePath));
     }
+    /**
+     * 
+     * @summary 获取租户下的用户列表
+     * @param {TenantIdInput} [body] 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof SysTenantApi
+     */
+    public async apiSysTenantUserListPost(body?: TenantIdInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultListSysUser>> {
+        return SysTenantApiFp(this.configuration).apiSysTenantUserListPost(body, options).then((request) => request(this.axios, this.basePath));
+    }
 }

+ 94 - 0
Web/src/api-services/models/add-open-access-input.ts

@@ -0,0 +1,94 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+import { SysTenant } from './sys-tenant';
+import { SysUser } from './sys-user';
+/**
+ * 
+ * @export
+ * @interface AddOpenAccessInput
+ */
+export interface AddOpenAccessInput {
+    /**
+     * 雪花Id
+     * @type {number}
+     * @memberof AddOpenAccessInput
+     */
+    id?: number;
+    /**
+     * 创建时间
+     * @type {Date}
+     * @memberof AddOpenAccessInput
+     */
+    createTime?: Date | null;
+    /**
+     * 更新时间
+     * @type {Date}
+     * @memberof AddOpenAccessInput
+     */
+    updateTime?: Date | null;
+    /**
+     * 创建者Id
+     * @type {number}
+     * @memberof AddOpenAccessInput
+     */
+    createUserId?: number | null;
+    /**
+     * 修改者Id
+     * @type {number}
+     * @memberof AddOpenAccessInput
+     */
+    updateUserId?: number | null;
+    /**
+     * 软删除
+     * @type {boolean}
+     * @memberof AddOpenAccessInput
+     */
+    isDelete?: boolean;
+    /**
+     * 
+     * @type {SysUser}
+     * @memberof AddOpenAccessInput
+     */
+    bindUser?: SysUser;
+    /**
+     * 绑定租户Id
+     * @type {number}
+     * @memberof AddOpenAccessInput
+     */
+    bindTenantId?: number;
+    /**
+     * 
+     * @type {SysTenant}
+     * @memberof AddOpenAccessInput
+     */
+    bindTenant?: SysTenant;
+    /**
+     * 身份标识
+     * @type {string}
+     * @memberof AddOpenAccessInput
+     */
+    accessKey: string;
+    /**
+     * 密钥
+     * @type {string}
+     * @memberof AddOpenAccessInput
+     */
+    accessSecret: string;
+    /**
+     * 绑定用户Id
+     * @type {number}
+     * @memberof AddOpenAccessInput
+     */
+    bindUserId: number;
+}

+ 57 - 0
Web/src/api-services/models/admin-result-list-sys-user.ts

@@ -0,0 +1,57 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+import { SysUser } from './sys-user';
+/**
+ * 全局返回结果
+ * @export
+ * @interface AdminResultListSysUser
+ */
+export interface AdminResultListSysUser {
+    /**
+     * 状态码
+     * @type {number}
+     * @memberof AdminResultListSysUser
+     */
+    code?: number;
+    /**
+     * 类型success、warning、error
+     * @type {string}
+     * @memberof AdminResultListSysUser
+     */
+    type?: string | null;
+    /**
+     * 错误信息
+     * @type {string}
+     * @memberof AdminResultListSysUser
+     */
+    message?: string | null;
+    /**
+     * 数据
+     * @type {Array<SysUser>}
+     * @memberof AdminResultListSysUser
+     */
+    result?: Array<SysUser> | null;
+    /**
+     * 附加数据
+     * @type {any}
+     * @memberof AdminResultListSysUser
+     */
+    extras?: any | null;
+    /**
+     * 时间
+     * @type {Date}
+     * @memberof AdminResultListSysUser
+     */
+    time?: Date;
+}

+ 57 - 0
Web/src/api-services/models/admin-result-sql-sugar-paged-list-open-access-output.ts

@@ -0,0 +1,57 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+import { SqlSugarPagedListOpenAccessOutput } from './sql-sugar-paged-list-open-access-output';
+/**
+ * 全局返回结果
+ * @export
+ * @interface AdminResultSqlSugarPagedListOpenAccessOutput
+ */
+export interface AdminResultSqlSugarPagedListOpenAccessOutput {
+    /**
+     * 状态码
+     * @type {number}
+     * @memberof AdminResultSqlSugarPagedListOpenAccessOutput
+     */
+    code?: number;
+    /**
+     * 类型success、warning、error
+     * @type {string}
+     * @memberof AdminResultSqlSugarPagedListOpenAccessOutput
+     */
+    type?: string | null;
+    /**
+     * 错误信息
+     * @type {string}
+     * @memberof AdminResultSqlSugarPagedListOpenAccessOutput
+     */
+    message?: string | null;
+    /**
+     * 
+     * @type {SqlSugarPagedListOpenAccessOutput}
+     * @memberof AdminResultSqlSugarPagedListOpenAccessOutput
+     */
+    result?: SqlSugarPagedListOpenAccessOutput;
+    /**
+     * 附加数据
+     * @type {any}
+     * @memberof AdminResultSqlSugarPagedListOpenAccessOutput
+     */
+    extras?: any | null;
+    /**
+     * 时间
+     * @type {Date}
+     * @memberof AdminResultSqlSugarPagedListOpenAccessOutput
+     */
+    time?: Date;
+}

+ 57 - 0
Web/src/api-services/models/admin-result-sql-sugar-paged-list-sys-open-access.ts

@@ -0,0 +1,57 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+import { SqlSugarPagedListSysOpenAccess } from './sql-sugar-paged-list-sys-open-access';
+/**
+ * 全局返回结果
+ * @export
+ * @interface AdminResultSqlSugarPagedListSysOpenAccess
+ */
+export interface AdminResultSqlSugarPagedListSysOpenAccess {
+    /**
+     * 状态码
+     * @type {number}
+     * @memberof AdminResultSqlSugarPagedListSysOpenAccess
+     */
+    code?: number;
+    /**
+     * 类型success、warning、error
+     * @type {string}
+     * @memberof AdminResultSqlSugarPagedListSysOpenAccess
+     */
+    type?: string | null;
+    /**
+     * 错误信息
+     * @type {string}
+     * @memberof AdminResultSqlSugarPagedListSysOpenAccess
+     */
+    message?: string | null;
+    /**
+     * 
+     * @type {SqlSugarPagedListSysOpenAccess}
+     * @memberof AdminResultSqlSugarPagedListSysOpenAccess
+     */
+    result?: SqlSugarPagedListSysOpenAccess;
+    /**
+     * 附加数据
+     * @type {any}
+     * @memberof AdminResultSqlSugarPagedListSysOpenAccess
+     */
+    extras?: any | null;
+    /**
+     * 时间
+     * @type {Date}
+     * @memberof AdminResultSqlSugarPagedListSysOpenAccess
+     */
+    time?: Date;
+}

+ 26 - 0
Web/src/api-services/models/delete-open-access-input.ts

@@ -0,0 +1,26 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+/**
+ * 
+ * @export
+ * @interface DeleteOpenAccessInput
+ */
+export interface DeleteOpenAccessInput {
+    /**
+     * 主键Id
+     * @type {number}
+     * @memberof DeleteOpenAccessInput
+     */
+    id: number;
+}

+ 10 - 0
Web/src/api-services/models/index.ts

@@ -7,6 +7,7 @@ export * from './add-job-detail-input';
 export * from './add-job-trigger-input';
 export * from './add-menu-input';
 export * from './add-notice-input';
+export * from './add-open-access-input';
 export * from './add-org-input';
 export * from './add-plugin-input';
 export * from './add-pos-input';
@@ -46,12 +47,14 @@ export * from './admin-result-list-sys-notice';
 export * from './admin-result-list-sys-org';
 export * from './admin-result-list-sys-pos';
 export * from './admin-result-list-sys-region';
+export * from './admin-result-list-sys-user';
 export * from './admin-result-list-sys-user-ext-org';
 export * from './admin-result-list-table-output';
 export * from './admin-result-login-output';
 export * from './admin-result-login-user-output';
 export * from './admin-result-object';
 export * from './admin-result-sql-sugar-paged-list-job-output';
+export * from './admin-result-sql-sugar-paged-list-open-access-output';
 export * from './admin-result-sql-sugar-paged-list-sys-code-gen';
 export * from './admin-result-sql-sugar-paged-list-sys-config';
 export * from './admin-result-sql-sugar-paged-list-sys-dict-data';
@@ -113,6 +116,7 @@ export * from './delete-job-trigger-input';
 export * from './delete-menu-input';
 export * from './delete-message-template-input';
 export * from './delete-notice-input';
+export * from './delete-open-access-input';
 export * from './delete-org-input';
 export * from './delete-plugin-input';
 export * from './delete-pos-input';
@@ -151,6 +155,8 @@ export * from './notice-input';
 export * from './notice-status-enum';
 export * from './notice-type-enum';
 export * from './notice-user-status-enum';
+export * from './open-access-input';
+export * from './open-access-output';
 export * from './page-config-input';
 export * from './page-dict-data-input';
 export * from './page-dict-type-input';
@@ -174,6 +180,7 @@ export * from './role-output';
 export * from './send-subscribe-message-input';
 export * from './signature-input';
 export * from './sql-sugar-paged-list-job-output';
+export * from './sql-sugar-paged-list-open-access-output';
 export * from './sql-sugar-paged-list-sys-code-gen';
 export * from './sql-sugar-paged-list-sys-config';
 export * from './sql-sugar-paged-list-sys-dict-data';
@@ -223,11 +230,13 @@ export * from './sys-pos';
 export * from './sys-print';
 export * from './sys-region';
 export * from './sys-role';
+export * from './sys-tenant';
 export * from './sys-user';
 export * from './sys-user-ext-org';
 export * from './sys-wechat-pay';
 export * from './sys-wechat-user';
 export * from './table-output';
+export * from './tenant-id-input';
 export * from './tenant-input';
 export * from './tenant-output';
 export * from './tenant-type-enum';
@@ -243,6 +252,7 @@ export * from './update-job-detail-input';
 export * from './update-job-trigger-input';
 export * from './update-menu-input';
 export * from './update-notice-input';
+export * from './update-open-access-input';
 export * from './update-org-input';
 export * from './update-plugin-input';
 export * from './update-pos-input';

+ 56 - 0
Web/src/api-services/models/open-access-input.ts

@@ -0,0 +1,56 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+/**
+ * 开放接口身份输入参数
+ * @export
+ * @interface OpenAccessInput
+ */
+export interface OpenAccessInput {
+    /**
+     * 当前页码
+     * @type {number}
+     * @memberof OpenAccessInput
+     */
+    page?: number;
+    /**
+     * 页码容量
+     * @type {number}
+     * @memberof OpenAccessInput
+     */
+    pageSize?: number;
+    /**
+     * 排序字段
+     * @type {string}
+     * @memberof OpenAccessInput
+     */
+    field?: string | null;
+    /**
+     * 排序方向
+     * @type {string}
+     * @memberof OpenAccessInput
+     */
+    order?: string | null;
+    /**
+     * 降序排序
+     * @type {string}
+     * @memberof OpenAccessInput
+     */
+    descStr?: string | null;
+    /**
+     * 身份标识
+     * @type {string}
+     * @memberof OpenAccessInput
+     */
+    accessKey?: string | null;
+}

+ 106 - 0
Web/src/api-services/models/open-access-output.ts

@@ -0,0 +1,106 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+import { SysTenant } from './sys-tenant';
+import { SysUser } from './sys-user';
+/**
+ * 
+ * @export
+ * @interface OpenAccessOutput
+ */
+export interface OpenAccessOutput {
+    /**
+     * 雪花Id
+     * @type {number}
+     * @memberof OpenAccessOutput
+     */
+    id?: number;
+    /**
+     * 创建时间
+     * @type {Date}
+     * @memberof OpenAccessOutput
+     */
+    createTime?: Date | null;
+    /**
+     * 更新时间
+     * @type {Date}
+     * @memberof OpenAccessOutput
+     */
+    updateTime?: Date | null;
+    /**
+     * 创建者Id
+     * @type {number}
+     * @memberof OpenAccessOutput
+     */
+    createUserId?: number | null;
+    /**
+     * 修改者Id
+     * @type {number}
+     * @memberof OpenAccessOutput
+     */
+    updateUserId?: number | null;
+    /**
+     * 软删除
+     * @type {boolean}
+     * @memberof OpenAccessOutput
+     */
+    isDelete?: boolean;
+    /**
+     * 身份标识
+     * @type {string}
+     * @memberof OpenAccessOutput
+     */
+    accessKey?: string | null;
+    /**
+     * 密钥
+     * @type {string}
+     * @memberof OpenAccessOutput
+     */
+    accessSecret?: string | null;
+    /**
+     * 绑定用户Id
+     * @type {number}
+     * @memberof OpenAccessOutput
+     */
+    bindUserId?: number;
+    /**
+     * 
+     * @type {SysUser}
+     * @memberof OpenAccessOutput
+     */
+    bindUser?: SysUser;
+    /**
+     * 绑定租户Id
+     * @type {number}
+     * @memberof OpenAccessOutput
+     */
+    bindTenantId?: number;
+    /**
+     * 
+     * @type {SysTenant}
+     * @memberof OpenAccessOutput
+     */
+    bindTenant?: SysTenant;
+    /**
+     * 绑定用户账号
+     * @type {string}
+     * @memberof OpenAccessOutput
+     */
+    bindUserAccount?: string | null;
+    /**
+     * 绑定租户名称
+     * @type {string}
+     * @memberof OpenAccessOutput
+     */
+    bindTenantName?: string | null;
+}

+ 63 - 0
Web/src/api-services/models/sql-sugar-paged-list-open-access-output.ts

@@ -0,0 +1,63 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+import { OpenAccessOutput } from './open-access-output';
+/**
+ * 分页泛型集合
+ * @export
+ * @interface SqlSugarPagedListOpenAccessOutput
+ */
+export interface SqlSugarPagedListOpenAccessOutput {
+    /**
+     * 页码
+     * @type {number}
+     * @memberof SqlSugarPagedListOpenAccessOutput
+     */
+    page?: number;
+    /**
+     * 页容量
+     * @type {number}
+     * @memberof SqlSugarPagedListOpenAccessOutput
+     */
+    pageSize?: number;
+    /**
+     * 总条数
+     * @type {number}
+     * @memberof SqlSugarPagedListOpenAccessOutput
+     */
+    total?: number;
+    /**
+     * 总页数
+     * @type {number}
+     * @memberof SqlSugarPagedListOpenAccessOutput
+     */
+    totalPages?: number;
+    /**
+     * 当前页集合
+     * @type {Array<OpenAccessOutput>}
+     * @memberof SqlSugarPagedListOpenAccessOutput
+     */
+    items?: Array<OpenAccessOutput> | null;
+    /**
+     * 是否有上一页
+     * @type {boolean}
+     * @memberof SqlSugarPagedListOpenAccessOutput
+     */
+    hasPrevPage?: boolean;
+    /**
+     * 是否有下一页
+     * @type {boolean}
+     * @memberof SqlSugarPagedListOpenAccessOutput
+     */
+    hasNextPage?: boolean;
+}

+ 63 - 0
Web/src/api-services/models/sql-sugar-paged-list-sys-open-access.ts

@@ -0,0 +1,63 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+import { SysOpenAccess } from './sys-open-access';
+/**
+ * 分页泛型集合
+ * @export
+ * @interface SqlSugarPagedListSysOpenAccess
+ */
+export interface SqlSugarPagedListSysOpenAccess {
+    /**
+     * 页码
+     * @type {number}
+     * @memberof SqlSugarPagedListSysOpenAccess
+     */
+    page?: number;
+    /**
+     * 页容量
+     * @type {number}
+     * @memberof SqlSugarPagedListSysOpenAccess
+     */
+    pageSize?: number;
+    /**
+     * 总条数
+     * @type {number}
+     * @memberof SqlSugarPagedListSysOpenAccess
+     */
+    total?: number;
+    /**
+     * 总页数
+     * @type {number}
+     * @memberof SqlSugarPagedListSysOpenAccess
+     */
+    totalPages?: number;
+    /**
+     * 当前页集合
+     * @type {Array<SysOpenAccess>}
+     * @memberof SqlSugarPagedListSysOpenAccess
+     */
+    items?: Array<SysOpenAccess> | null;
+    /**
+     * 是否有上一页
+     * @type {boolean}
+     * @memberof SqlSugarPagedListSysOpenAccess
+     */
+    hasPrevPage?: boolean;
+    /**
+     * 是否有下一页
+     * @type {boolean}
+     * @memberof SqlSugarPagedListSysOpenAccess
+     */
+    hasNextPage?: boolean;
+}

+ 81 - 0
Web/src/api-services/models/sys-open-access.ts

@@ -0,0 +1,81 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+import { SysUser } from './sys-user';
+/**
+ * 开放接口身份表
+ * @export
+ * @interface SysOpenAccess
+ */
+export interface SysOpenAccess {
+    /**
+     * 雪花Id
+     * @type {number}
+     * @memberof SysOpenAccess
+     */
+    id?: number;
+    /**
+     * 创建时间
+     * @type {Date}
+     * @memberof SysOpenAccess
+     */
+    createTime?: Date | null;
+    /**
+     * 更新时间
+     * @type {Date}
+     * @memberof SysOpenAccess
+     */
+    updateTime?: Date | null;
+    /**
+     * 创建者Id
+     * @type {number}
+     * @memberof SysOpenAccess
+     */
+    createUserId?: number | null;
+    /**
+     * 修改者Id
+     * @type {number}
+     * @memberof SysOpenAccess
+     */
+    updateUserId?: number | null;
+    /**
+     * 软删除
+     * @type {boolean}
+     * @memberof SysOpenAccess
+     */
+    isDelete?: boolean;
+    /**
+     * 身份标识
+     * @type {string}
+     * @memberof SysOpenAccess
+     */
+    accessKey?: string | null;
+    /**
+     * 密钥
+     * @type {string}
+     * @memberof SysOpenAccess
+     */
+    accessSecret?: string | null;
+    /**
+     * 绑定用户Id
+     * @type {number}
+     * @memberof SysOpenAccess
+     */
+    bindUserId?: number;
+    /**
+     * 
+     * @type {SysUser}
+     * @memberof SysOpenAccess
+     */
+    bindUser?: SysUser;
+}

+ 119 - 0
Web/src/api-services/models/sys-tenant.ts

@@ -0,0 +1,119 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+import { DbType } from './db-type';
+import { StatusEnum } from './status-enum';
+import { TenantTypeEnum } from './tenant-type-enum';
+/**
+ * 系统租户表
+ * @export
+ * @interface SysTenant
+ */
+export interface SysTenant {
+    /**
+     * 雪花Id
+     * @type {number}
+     * @memberof SysTenant
+     */
+    id?: number;
+    /**
+     * 创建时间
+     * @type {Date}
+     * @memberof SysTenant
+     */
+    createTime?: Date | null;
+    /**
+     * 更新时间
+     * @type {Date}
+     * @memberof SysTenant
+     */
+    updateTime?: Date | null;
+    /**
+     * 创建者Id
+     * @type {number}
+     * @memberof SysTenant
+     */
+    createUserId?: number | null;
+    /**
+     * 修改者Id
+     * @type {number}
+     * @memberof SysTenant
+     */
+    updateUserId?: number | null;
+    /**
+     * 软删除
+     * @type {boolean}
+     * @memberof SysTenant
+     */
+    isDelete?: boolean;
+    /**
+     * 用户Id
+     * @type {number}
+     * @memberof SysTenant
+     */
+    userId?: number;
+    /**
+     * 机构Id
+     * @type {number}
+     * @memberof SysTenant
+     */
+    orgId?: number;
+    /**
+     * 主机
+     * @type {string}
+     * @memberof SysTenant
+     */
+    host?: string | null;
+    /**
+     * 
+     * @type {TenantTypeEnum}
+     * @memberof SysTenant
+     */
+    tenantType?: TenantTypeEnum;
+    /**
+     * 
+     * @type {DbType}
+     * @memberof SysTenant
+     */
+    dbType?: DbType;
+    /**
+     * 数据库连接
+     * @type {string}
+     * @memberof SysTenant
+     */
+    connection?: string | null;
+    /**
+     * 数据库标识
+     * @type {string}
+     * @memberof SysTenant
+     */
+    configId?: string | null;
+    /**
+     * 排序
+     * @type {number}
+     * @memberof SysTenant
+     */
+    orderNo?: number;
+    /**
+     * 备注
+     * @type {string}
+     * @memberof SysTenant
+     */
+    remark?: string | null;
+    /**
+     * 
+     * @type {StatusEnum}
+     * @memberof SysTenant
+     */
+    status?: StatusEnum;
+}

+ 26 - 0
Web/src/api-services/models/tenant-id-input.ts

@@ -0,0 +1,26 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+/**
+ * 
+ * @export
+ * @interface TenantIdInput
+ */
+export interface TenantIdInput {
+    /**
+     * 租户Id
+     * @type {number}
+     * @memberof TenantIdInput
+     */
+    tenantId?: number;
+}

+ 94 - 0
Web/src/api-services/models/update-open-access-input.ts

@@ -0,0 +1,94 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET
+ * 让 .NET 开发更简单、更通用、更流行。前后端分离架构(.NET6/Vue3),开箱即用紧随前沿技术。<br/><a href='https://gitee.com/zuohuaijun/Admin.NET/'>https://gitee.com/zuohuaijun/Admin.NET</a>
+ *
+ * OpenAPI spec version: 1.0.0
+ * Contact: 515096995@qq.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+import { SysTenant } from './sys-tenant';
+import { SysUser } from './sys-user';
+/**
+ * 
+ * @export
+ * @interface UpdateOpenAccessInput
+ */
+export interface UpdateOpenAccessInput {
+    /**
+     * 雪花Id
+     * @type {number}
+     * @memberof UpdateOpenAccessInput
+     */
+    id?: number;
+    /**
+     * 创建时间
+     * @type {Date}
+     * @memberof UpdateOpenAccessInput
+     */
+    createTime?: Date | null;
+    /**
+     * 更新时间
+     * @type {Date}
+     * @memberof UpdateOpenAccessInput
+     */
+    updateTime?: Date | null;
+    /**
+     * 创建者Id
+     * @type {number}
+     * @memberof UpdateOpenAccessInput
+     */
+    createUserId?: number | null;
+    /**
+     * 修改者Id
+     * @type {number}
+     * @memberof UpdateOpenAccessInput
+     */
+    updateUserId?: number | null;
+    /**
+     * 软删除
+     * @type {boolean}
+     * @memberof UpdateOpenAccessInput
+     */
+    isDelete?: boolean;
+    /**
+     * 
+     * @type {SysUser}
+     * @memberof UpdateOpenAccessInput
+     */
+    bindUser?: SysUser;
+    /**
+     * 绑定租户Id
+     * @type {number}
+     * @memberof UpdateOpenAccessInput
+     */
+    bindTenantId?: number;
+    /**
+     * 
+     * @type {SysTenant}
+     * @memberof UpdateOpenAccessInput
+     */
+    bindTenant?: SysTenant;
+    /**
+     * 身份标识
+     * @type {string}
+     * @memberof UpdateOpenAccessInput
+     */
+    accessKey: string;
+    /**
+     * 密钥
+     * @type {string}
+     * @memberof UpdateOpenAccessInput
+     */
+    accessSecret: string;
+    /**
+     * 绑定用户Id
+     * @type {number}
+     * @memberof UpdateOpenAccessInput
+     */
+    bindUserId: number;
+}

+ 123 - 0
Web/src/views/system/openAccess/component/editOpenAccess.vue

@@ -0,0 +1,123 @@
+<template>
+	<div class="sys-notice-container">
+		<el-dialog v-model="state.isShowDialog" draggable :close-on-click-modal="false" width="600px">
+			<template #header>
+				<div style="color: #fff">
+					<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Edit /> </el-icon>
+					<span> {{ props.title }} </span>
+				</div>
+			</template>
+			<el-form :model="state.ruleForm" ref="ruleFormRef" label-width="auto">
+				<el-row :gutter="35">
+					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+						<el-form-item label="身份标识" prop="accessKey" :rules="[{ required: true, message: '身份标识不能为空', trigger: 'blur' }]">
+							<el-input v-model="state.ruleForm.accessKey" placeholder="身份标识" clearable />
+						</el-form-item>
+					</el-col>
+					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+						<el-form-item label="密钥" prop="accessSecret" :rules="[{ required: true, message: '密钥不能为空', trigger: 'blur' }]">
+							<el-input v-model="state.ruleForm.accessSecret" placeholder="密钥" clearable>
+								<template #append>
+									<el-button @click="createSecret">生成密钥</el-button>
+								</template>
+							</el-input>
+						</el-form-item>
+					</el-col>
+					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+						<el-form-item label="绑定租户" prop="bindTenantId" :rules="[{ required: true, message: '绑定租户不能为空', trigger: 'blur' }]">
+							<el-select v-model="state.ruleForm.bindTenantId" placeholder="绑定租户" filterable default-first-option style="width: 100%" @change="tenantChange">
+								<el-option v-for="item in state.tenantData" :key="item.id" :label="item.name" :value="item.id" />
+							</el-select>
+						</el-form-item>
+					</el-col>
+					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+						<el-form-item label="绑定用户" prop="bindUserId" :rules="[{ required: true, message: '绑定用户不能为空', trigger: 'blur' }]">
+							<el-select v-model="state.ruleForm.bindUserId" placeholder="绑定用户" filterable default-first-option style="width: 100%">
+								<el-option v-for="item in state.userData" :key="item.id" :label="`${item.account}(${item.realName})`" :value="item.id" />
+							</el-select>
+						</el-form-item>
+					</el-col>
+				</el-row>
+			</el-form>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="cancel">取 消</el-button>
+					<el-button type="primary" @click="submit">确 定</el-button>
+				</span>
+			</template>
+		</el-dialog>
+	</div>
+</template>
+
+<script lang="ts" setup name="sysOpenAccessEdit">
+import { onMounted, reactive, ref } from 'vue';
+
+import { getAPI } from '/@/utils/axios-utils';
+import { SysOpenAccessApi, SysTenantApi } from '/@/api-services/api';
+import { SysUser, TenantOutput, UpdateOpenAccessInput } from '/@/api-services/models';
+
+const props = defineProps({
+	title: String,
+});
+const emits = defineEmits(['handleQuery']);
+const ruleFormRef = ref();
+const state = reactive({
+	isShowDialog: false,
+	ruleForm: {} as UpdateOpenAccessInput,
+	tenantData: [] as Array<TenantOutput>, // 租户数据
+	userData: [] as Array<SysUser>, // 用户数据
+});
+
+onMounted(async () => {
+	var res = await getAPI(SysTenantApi).apiSysTenantPagePost({ page: 1, pageSize: 10000 });
+	state.tenantData = res.data.result?.items ?? [];
+});
+
+// 打开弹窗
+const openDialog = (row: any) => {
+	state.ruleForm = JSON.parse(JSON.stringify(row));
+	state.isShowDialog = true;
+	ruleFormRef.value?.resetFields();
+
+	tenantChange();
+};
+
+// 关闭弹窗
+const closeDialog = () => {
+	emits('handleQuery');
+	state.isShowDialog = false;
+};
+
+// 取消
+const cancel = () => {
+	state.isShowDialog = false;
+};
+
+// 提交
+const submit = () => {
+	ruleFormRef.value.validate(async (valid: boolean) => {
+		if (!valid) return;
+		if (state.ruleForm.id != undefined && state.ruleForm.id > 0) {
+			await getAPI(SysOpenAccessApi).apiSysOpenAccessUpdatePost(state.ruleForm);
+		} else {
+			await getAPI(SysOpenAccessApi).apiSysOpenAccessAddPost(state.ruleForm);
+		}
+		closeDialog();
+	});
+};
+
+/** 租户值变更 */
+const tenantChange = async () => {
+	var res = await getAPI(SysTenantApi).apiSysTenantUserListPost({ tenantId: state.ruleForm.bindTenantId ?? 0 });
+	state.userData = res.data.result ?? [];
+};
+
+/** 生成密钥 */
+const createSecret = async () => {
+	var res = await getAPI(SysOpenAccessApi).apiSysOpenAccessSecretPost();
+	state.ruleForm.accessSecret = res.data.result!;
+};
+
+// 导出对象
+defineExpose({ openDialog });
+</script>

+ 99 - 0
Web/src/views/system/openAccess/component/helpView.vue

@@ -0,0 +1,99 @@
+<template>
+	<div class="sys-notice-container">
+		<el-dialog v-model="state.isShowDialog" draggable :close-on-click-modal="false" width="800px">
+			<template #header>
+				<div style="color: #fff">
+					<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-QuestionFilled /> </el-icon>
+					<span> 说明 </span>
+				</div>
+			</template>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="close">关 闭</el-button>
+				</span>
+			</template>
+			<div class="text-content">
+				<h2>OpenAPI 使用</h2>
+				<ul>
+					<li>
+						在需要使用 Signature 身份验证的 Api 中贴上
+						<p><el-tag>[Authorize(AuthenticationSchemes = SignatureAuthenticationDefaults.AuthenticationScheme)]</el-tag></p>
+					</li>
+					<li>
+						通过对请求的签名,可以达到以下目的:
+						<ul>
+							<li>免登录识别访问接口用户的身份</li>
+							<li>防止潜在的重放攻击</li>
+						</ul>
+					</li>
+				</ul>
+				<el-divider />
+				<h2>OpenAPI 签名流程</h2>
+				客户端在请求时,需要按照如下步骤生成签名 Signature,并添加公共参数:
+				<h3>公共请求参数</h3>
+				<p>在原始请求的基础上添加 Header 请求参数</p>
+				<ul>
+					<li><el-tag effect="plain">accessKey</el-tag>:身份标识</li>
+					<li><el-tag effect="plain">timestamp</el-tag>:时间戳,精确到秒</li>
+					<li><el-tag effect="plain">nonce</el-tag>:唯一随机数,建议为一个6位的随机数</li>
+					<li><el-tag effect="plain">sign</el-tag>:签名数据(见“计算签名”部分)</li>
+				</ul>
+				<h3>计算签名</h3>
+				<ul>
+					<li>
+						按照如下顺序对请求中的参数进行排序,各个参数通过&进行拼接(中间不含空格):
+						<p><el-tag>method & url & accessKey & timestamp & nonce</el-tag></p>
+						<ul>
+							<li><el-tag effect="plain">method</el-tag> 需要大写,如:GET</li>
+							<li><el-tag effect="plain">url</el-tag> 去除协议、域名、参数,以 / 开头,如:/api/demo/helloWord</li>
+						</ul>
+					</li>
+					<li>使用 HMAC-SHA256 协议创建基于哈希的消息身份验证代码 (HMAC),以 <el-tag effect="plain">appSecret</el-tag> 作为密钥,对上面拼接的参数进行计算签名,所得签名进行 Base-64 编码</li>
+				</ul>
+			</div>
+		</el-dialog>
+	</div>
+</template>
+
+<script lang="ts" setup name="sysOpenAccessHelpView">
+import { reactive } from 'vue';
+
+const state = reactive({
+	isShowDialog: false,
+});
+
+// 打开弹窗
+const openDialog = () => {
+	state.isShowDialog = true;
+};
+
+// 关闭
+const close = () => {
+	state.isShowDialog = false;
+};
+
+// 导出对象
+defineExpose({ openDialog });
+</script>
+<style scoped lang="scss">
+.text-content {
+	h1 {
+		margin: 8px 0;
+	}
+	h2 {
+		margin: 8px 0;
+	}
+	h3 {
+		margin: 8px 0;
+	}
+	p {
+		margin: 8px 0;
+	}
+	ul {
+		padding: 0 0 0 30px;
+		li {
+			margin: 8px 0;
+		}
+	}
+}
+</style>

+ 143 - 0
Web/src/views/system/openAccess/index.vue

@@ -0,0 +1,143 @@
+<template>
+	<div class="sys-open-access-container">
+		<el-card shadow="hover" :body-style="{ paddingBottom: '0' }">
+			<el-form :model="state.queryParams" ref="queryForm" :inline="true">
+				<el-form-item label="身份标识">
+					<el-input v-model="state.queryParams.accessKey" placeholder="身份标识" clearable />
+				</el-form-item>
+				<el-form-item>
+					<el-button-group>
+						<el-button type="primary" icon="ele-Search" @click="handleQuery" v-auth="'sysOpenAccess:page'"> 查询 </el-button>
+						<el-button icon="ele-Refresh" @click="resetQuery"> 重置 </el-button>
+					</el-button-group>
+				</el-form-item>
+				<el-form-item>
+					<el-button type="primary" icon="ele-Plus" @click="openAddOpenAccess" v-auth="'sysOpenAccess:add'"> 新增 </el-button>
+					<el-button icon="ele-QuestionFilled" @click="openHelp"> 说明 </el-button>
+				</el-form-item>
+			</el-form>
+		</el-card>
+
+		<el-card class="full-table" shadow="hover" style="margin-top: 8px">
+			<el-table :data="state.openAccessData" style="width: 100%" v-loading="state.loading" border>
+				<el-table-column type="index" label="序号" width="55" align="center" />
+				<el-table-column prop="accessKey" label="身份标识" header-align="center" show-overflow-tooltip />
+				<el-table-column prop="accessSecret" label="密钥" header-align="center" show-overflow-tooltip />
+				<el-table-column prop="bindUserAccount" label="绑定用户账号" header-align="center" show-overflow-tooltip />
+				<el-table-column prop="bindTenantName" label="绑定租户名称" header-align="center" show-overflow-tooltip />
+				<el-table-column prop="createTime" label="创建时间" align="center" show-overflow-tooltip />
+				<el-table-column label="操作" width="200" fixed="right" align="center" show-overflow-tooltip>
+					<template #default="scope">
+						<el-button icon="ele-Edit" size="small" text type="primary" @click="openEditOpenAccess(scope.row)" v-auth="'sysOpenAccess:update'" :disabled="scope.row.status === 1"> 编辑 </el-button>
+						<el-button icon="ele-Delete" size="small" text type="danger" @click="delOpenAccess(scope.row)" v-auth="'sysOpenAccess:delete'" :disabled="scope.row.status === 1"> 删除 </el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+			<el-pagination
+				v-model:currentPage="state.tableParams.page"
+				v-model:page-size="state.tableParams.pageSize"
+				:total="state.tableParams.total"
+				:page-sizes="[10, 20, 50, 100]"
+				small
+				background
+				@size-change="handleSizeChange"
+				@current-change="handleCurrentChange"
+				layout="total, sizes, prev, pager, next, jumper"
+			/>
+		</el-card>
+
+		<EditOpenAccess ref="editOpenAccessRef" :title="state.editOpenAccessTitle" @handleQuery="handleQuery" />
+		<HelpView ref="helpViewRef" />
+	</div>
+</template>
+
+<script lang="ts" setup name="sysNotice">
+import { onMounted, reactive, ref } from 'vue';
+import { ElMessageBox, ElMessage } from 'element-plus';
+import EditOpenAccess from '/@/views/system/openAccess/component/editOpenAccess.vue';
+import HelpView from '/@/views/system/openAccess/component/helpView.vue';
+
+import { getAPI } from '/@/utils/axios-utils';
+import { SysOpenAccessApi } from '/@/api-services/api';
+import { OpenAccessOutput } from '/@/api-services/models';
+
+const editOpenAccessRef = ref<InstanceType<typeof EditOpenAccess>>();
+const helpViewRef = ref<InstanceType<typeof HelpView>>();
+const state = reactive({
+	loading: false,
+	openAccessData: [] as Array<OpenAccessOutput>,
+	queryParams: {
+		accessKey: undefined,
+	},
+	tableParams: {
+		page: 1,
+		pageSize: 20,
+		total: 0 as any,
+	},
+	editOpenAccessTitle: '',
+});
+
+onMounted(async () => {
+	handleQuery();
+});
+
+// 查询操作
+const handleQuery = async () => {
+	state.loading = true;
+	let params = Object.assign(state.queryParams, state.tableParams);
+	var res = await getAPI(SysOpenAccessApi).apiSysOpenAccessPagePost(params);
+	state.openAccessData = res.data.result?.items ?? [];
+	state.tableParams.total = res.data.result?.total;
+	state.loading = false;
+};
+
+// 重置操作
+const resetQuery = () => {
+	state.queryParams.accessKey = undefined;
+	handleQuery();
+};
+
+// 打开新增页面
+const openAddOpenAccess = () => {
+	state.editOpenAccessTitle = '添加开放接口身份';
+	editOpenAccessRef.value?.openDialog({ type: 1 });
+};
+
+// 打开编辑页面
+const openEditOpenAccess = (row: any) => {
+	state.editOpenAccessTitle = '编辑开放接口身份';
+	editOpenAccessRef.value?.openDialog(row);
+};
+
+// 删除
+const delOpenAccess = (row: any) => {
+	ElMessageBox.confirm(`确定删除开放接口身份:【${row.accessKey}】?`, '提示', {
+		confirmButtonText: '确定',
+		cancelButtonText: '取消',
+		type: 'warning',
+	})
+		.then(async () => {
+			await getAPI(SysOpenAccessApi).apiSysOpenAccessDeletePost({ id: row.id });
+			handleQuery();
+			ElMessage.success('删除成功');
+		})
+		.catch(() => {});
+};
+
+// 改变页面容量
+const handleSizeChange = (val: number) => {
+	state.tableParams.pageSize = val;
+	handleQuery();
+};
+
+// 改变页码序号
+const handleCurrentChange = (val: number) => {
+	state.tableParams.page = val;
+	handleQuery();
+};
+
+// 打开说明页面
+const openHelp = () => {
+	helpViewRef.value?.openDialog();
+};
+</script>