diff --git a/.vscode/settings.json b/.vscode/settings.json index 1cb80bb..7c24122 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "chat.tools.terminal.autoApprove": { "ForEach-Object": true, - "dotnet list": true + "dotnet list": true, + "dotnet build": true } } \ No newline at end of file diff --git a/Avalonia-API/Authentication/ApiAuthEndpointService.cs b/Avalonia-API/Authentication/ApiAuthEndpointService.cs index a319352..8c97530 100644 --- a/Avalonia-API/Authentication/ApiAuthEndpointService.cs +++ b/Avalonia-API/Authentication/ApiAuthEndpointService.cs @@ -8,6 +8,10 @@ using System.Text.Json; namespace Avalonia_API.Authentication { + /// + /// API 鉴权端点服务,实现 , + /// 处理登录、刷新 Token 和登出操作,使用 JWT 与 Refresh Token 机制。 + /// public sealed class ApiAuthEndpointService( AppDataContext db, JwtTokenService jwtTokenService, @@ -18,6 +22,12 @@ namespace Avalonia_API.Authentication PropertyNameCaseInsensitive = true, }; + /// + /// 处理用户登录请求。根据账号(邮箱或用户名)查找或创建用户, + /// 生成 JWT Access Token 和 Refresh Token 并返回。 + /// + /// 服务端点上下文,包含请求体、请求头等信息。 + /// 包含 AccessToken、RefreshToken 及过期时间的认证响应。 public async Task LoginAsync(ServiceEndpointContext ctx) { var request = Deserialize(ctx.Body); @@ -56,6 +66,12 @@ namespace Avalonia_API.Authentication roles), "登录成功"); } + /// + /// 使用 Refresh Token 轮换新的 Access Token 和 Refresh Token。 + /// 旧的 Refresh Token 会被撤销并替换。 + /// + /// 服务端点上下文,包含请求体中的 RefreshToken。 + /// 新的 Token 对;若 Refresh Token 无效则返回 401 错误。 public async Task RefreshAsync(ServiceEndpointContext ctx) { var request = Deserialize(ctx.Body); @@ -88,6 +104,11 @@ namespace Avalonia_API.Authentication roles), "刷新成功"); } + /// + /// 处理用户登出请求,撤销指定的 Refresh Token。 + /// + /// 服务端点上下文,包含请求体中的 RefreshToken。 + /// 登出成功的响应。 public async Task LogoutAsync(ServiceEndpointContext ctx) { var request = Deserialize(ctx.Body); @@ -95,6 +116,12 @@ namespace Avalonia_API.Authentication return ResponseHelper.Succeed("退出成功"); } + /// + /// 将 JSON 请求体反序列化为指定类型。 + /// + /// 目标类型。 + /// JSON 请求体字符串,可为空。 + /// 反序列化后的对象;若 body 为空则返回默认值。 private static T? Deserialize(string? body) { return string.IsNullOrWhiteSpace(body) @@ -102,6 +129,11 @@ namespace Avalonia_API.Authentication : JsonSerializer.Deserialize(body, JsonOptions); } + /// + /// 从上下文的 Items 中提取 ASP.NET Core HttpContext,并获取客户端远程 IP 地址。 + /// + /// 服务端点上下文。 + /// 客户端 IP 地址字符串;若无法获取则返回 null。 private static string? GetRemoteIpAddress(ServiceEndpointContext ctx) { return ctx.Items.TryGetValue("HttpContext", out var value) && value is HttpContext httpContext @@ -109,6 +141,11 @@ namespace Avalonia_API.Authentication : null; } + /// + /// 规范化角色数组:去空白、去重(忽略大小写),为空时默认返回 Admin 角色。 + /// + /// 原始角色数组,可为 null。 + /// 规范化后的角色数组。 private static string[] NormalizeRoles(string[]? roles) { var normalized = roles? diff --git a/Avalonia-API/Authentication/JwtOptions.cs b/Avalonia-API/Authentication/JwtOptions.cs index 435bc28..ad72a1a 100644 --- a/Avalonia-API/Authentication/JwtOptions.cs +++ b/Avalonia-API/Authentication/JwtOptions.cs @@ -1,15 +1,33 @@ namespace Avalonia_API.Authentication { + /// + /// JWT 鉴权配置选项,从 appsettings.json 的 Jwt 节绑定。 + /// public sealed class JwtOptions { + /// + /// 获取或设置 Token 签发者。 + /// public string Issuer { get; set; } = "Avalonia-API"; + /// + /// 获取或设置 Token 受众。 + /// public string Audience { get; set; } = "Avalonia-Client"; + /// + /// 获取或设置签名密钥(至少 32 字节)。 + /// public string SigningKey { get; set; } = "change-this-development-signing-key-at-least-32-bytes"; + /// + /// 获取或设置 Access Token 有效期(分钟),默认 60 分钟。 + /// public int AccessTokenMinutes { get; set; } = 60; + /// + /// 获取或设置 Refresh Token 有效期(天),默认 30 天。 + /// public int RefreshTokenDays { get; set; } = 30; } } diff --git a/Avalonia-API/Authentication/JwtTokenService.cs b/Avalonia-API/Authentication/JwtTokenService.cs index 023529c..a76d501 100644 --- a/Avalonia-API/Authentication/JwtTokenService.cs +++ b/Avalonia-API/Authentication/JwtTokenService.cs @@ -7,10 +7,22 @@ using System.Text; namespace Avalonia_API.Authentication { + /// + /// JWT Token 服务,负责创建包含用户声明和角色的 Access Token。 + /// public sealed class JwtTokenService(IOptions options) { + /// + /// JWT 配置选项。 + /// private readonly JwtOptions _options = options.Value; + /// + /// 创建包含用户声明和角色的 JWT Access Token。 + /// + /// 用户实体。 + /// 角色集合。 + /// 包含 Token 字符串和过期时间的元组。 public (string Token, DateTime ExpiresAt) CreateAccessToken(UserEntity user, IReadOnlyCollection roles) { var expiresAt = DateTime.UtcNow.AddMinutes(_options.AccessTokenMinutes); diff --git a/Avalonia-API/Authentication/RefreshTokenService.cs b/Avalonia-API/Authentication/RefreshTokenService.cs index 40cfbcc..7b8e2f9 100644 --- a/Avalonia-API/Authentication/RefreshTokenService.cs +++ b/Avalonia-API/Authentication/RefreshTokenService.cs @@ -7,10 +7,25 @@ using System.Text; namespace Avalonia_API.Authentication { + /// + /// Refresh Token 服务,负责创建、查找、撤销和轮换 Refresh Token, + /// Token 原文经 SHA256 哈希后存入数据库以保证安全性。 + /// public sealed class RefreshTokenService(AppDataContext db, IOptions options) { + /// + /// JWT 配置选项。 + /// private readonly JwtOptions _options = options.Value; + /// + /// 创建一个新的 Refresh Token,生成随机 Token 原文并存储其哈希到数据库。 + /// + /// 关联的用户 ID。 + /// 创建设备标识(如 User-Agent)。 + /// 客户端 IP 地址。 + /// 取消令牌。 + /// 包含 Token 原文和实体记录的元组。 public async Task<(string Token, ApiRefreshTokenEntity Entity)> CreateAsync( int userId, string? device, @@ -32,6 +47,13 @@ namespace Avalonia_API.Authentication return (token, entity); } + /// + /// 查找有效的 Refresh Token 实体。Token 原文会被哈希后查询数据库, + /// 仅返回未过期且未被撤销的 Token。 + /// + /// Refresh Token 原文。 + /// 取消令牌。 + /// 有效的 Token 实体;若无效或不存在则返回 null。 public async Task FindActiveAsync(string? token, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(token)) @@ -44,6 +66,11 @@ namespace Avalonia_API.Authentication return entity?.IsActive == true ? entity : null; } + /// + /// 撤销指定的 Refresh Token,将其 RevokedAt 设为当前时间。 + /// + /// 要撤销的 Refresh Token 原文。 + /// 取消令牌。 public async Task RevokeAsync(string? token, CancellationToken cancellationToken = default) { var entity = await FindActiveAsync(token, cancellationToken); @@ -56,6 +83,14 @@ namespace Avalonia_API.Authentication await db.SaveChangesAsync(cancellationToken); } + /// + /// 轮换 Refresh Token:撤销旧的并创建新的,将新 Token 的哈希关联到旧记录。 + /// + /// 旧的 Refresh Token 原文。 + /// 当前设备标识。 + /// 当前客户端 IP 地址。 + /// 取消令牌。 + /// 新的 Token 对;若旧 Token 无效则返回 null。 public async Task<(string Token, ApiRefreshTokenEntity Entity)?> RotateAsync( string? token, string? device, @@ -75,6 +110,11 @@ namespace Avalonia_API.Authentication return next; } + /// + /// 对 Token 原文进行 SHA256 哈希,返回十六进制字符串。 + /// + /// Token 原文。 + /// SHA256 哈希后的十六进制字符串。 private static string HashToken(string token) { var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(token)); diff --git a/Avalonia-API/Configuration/ServicesConfiguration.cs b/Avalonia-API/Configuration/ServicesConfiguration.cs index d90e564..f8f8746 100644 --- a/Avalonia-API/Configuration/ServicesConfiguration.cs +++ b/Avalonia-API/Configuration/ServicesConfiguration.cs @@ -10,6 +10,9 @@ using System.Text; namespace Avalonia_API.Configuration { + /// + /// API 项目服务配置扩展类,负责注册数据库、鉴权、业务服务和统一端点。 + /// public static class ServicesConfiguration { /// diff --git a/Avalonia-API/Extensions/UnifiedEndpointExtensions.cs b/Avalonia-API/Extensions/UnifiedEndpointExtensions.cs index b09c1d8..234541f 100644 --- a/Avalonia-API/Extensions/UnifiedEndpointExtensions.cs +++ b/Avalonia-API/Extensions/UnifiedEndpointExtensions.cs @@ -95,6 +95,13 @@ namespace Avalonia_API.Extensions return routeBuilder; } + /// + /// 根据端点的 HTTP 方法(GET/POST/PUT/DELETE)将其映射到 ASP.NET Core 路由。 + /// + /// 路由组。 + /// 统一端点定义。 + /// 服务提供程序。 + /// 路由处理器构建器,用于叠加过滤器等配置。 private static RouteHandlerBuilder MapEndpoint( IEndpointRouteBuilder group, ServiceEndpoint endpoint, @@ -112,6 +119,12 @@ namespace Avalonia_API.Extensions }; } + /// + /// 创建适配 ASP.NET Core 的委托处理器,将统一处理器包装为 ASP.NET Core 可识别的委托。 + /// + /// 统一端点处理器。 + /// 服务提供程序。 + /// ASP.NET Core 兼容的委托。 private static Delegate CreateAspNetCoreHandler( Func> unifiedHandler, IServiceProvider serviceProvider) @@ -135,6 +148,12 @@ namespace Avalonia_API.Extensions }; } + /// + /// 从 ASP.NET Core 的 HttpContext 构建统一的 ServiceEndpointContext, + /// 提取路径、方法、请求头、查询参数和请求体。 + /// + /// ASP.NET Core 的 HttpContext。 + /// 构建好的统一端点上下文。 private static async Task BuildContextFromHttpContext(HttpContext httpContext) { var ctx = new ServiceEndpointContext @@ -166,6 +185,14 @@ namespace Avalonia_API.Extensions return ctx; } + /// + /// 将统一过滤器转换为 ASP.NET Core 端点过滤器, + /// 在调用统一过滤器前后桥接上下文和状态。 + /// + /// 统一过滤器。 + /// ASP.NET Core 过滤器调用上下文。 + /// ASP.NET Core 过滤器管道中的下一个委托。 + /// 过滤器执行结果,可能包含短路响应体。 private static async ValueTask ConvertFilterAsync( UnifiedFilter unifiedFilter, AspNetCoreFilterContext aspContext, diff --git a/Avalonia-Common/Core/ApiResponse.cs b/Avalonia-Common/Core/ApiResponse.cs index 3076c19..404e9c8 100644 --- a/Avalonia-Common/Core/ApiResponse.cs +++ b/Avalonia-Common/Core/ApiResponse.cs @@ -115,27 +115,56 @@ namespace Avalonia_Common.Core /// public class PagedResponse { + /// + /// 获取或设置操作是否成功。 + /// [JsonPropertyName("success")] public bool Success { get; set; } = true; + /// + /// 获取或设置业务状态码,默认 200。 + /// [JsonPropertyName("code")] public int Code { get; set; } = 200; + /// + /// 获取或设置分页数据项列表。 + /// [JsonPropertyName("items")] public List Items { get; set; } = new(); + /// + /// 获取或设置数据总条数。 + /// [JsonPropertyName("total")] public int Total { get; set; } + /// + /// 获取或设置当前页码,从 1 开始。 + /// [JsonPropertyName("page")] public int Page { get; set; } = 1; + /// + /// 获取或设置每页条数,默认 20。 + /// [JsonPropertyName("pageSize")] public int PageSize { get; set; } = 20; + /// + /// 获取总页数(根据 Total 和 PageSize 自动计算)。 + /// [JsonPropertyName("totalPages")] public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)Total / PageSize) : 0; + /// + /// 从数据列表和分页参数创建分页响应。 + /// + /// 当前页数据项。 + /// 数据总条数。 + /// 当前页码。 + /// 每页条数。 + /// 分页响应实例。 public static PagedResponse From(List items, int total, int page, int pageSize) { return new PagedResponse diff --git a/Avalonia-Common/Infrastructure/LoggingConfiguration.cs b/Avalonia-Common/Infrastructure/LoggingConfiguration.cs index 3f5c3d0..12df6fe 100644 --- a/Avalonia-Common/Infrastructure/LoggingConfiguration.cs +++ b/Avalonia-Common/Infrastructure/LoggingConfiguration.cs @@ -87,6 +87,9 @@ namespace Avalonia_Common.Infrastructure /// public static class AppLog { + /// + /// 保存全局日志记录器实例。 + /// private static ILogger? _logger; /// @@ -98,26 +101,66 @@ namespace Avalonia_Common.Infrastructure Log.Logger = logger; } + /// + /// 获取全局日志记录器。若未初始化则回退到 Serilog.Log.Logger。 + /// public static ILogger Logger => _logger ?? Log.Logger; + /// + /// 写入 Debug 级别日志。 + /// + /// 消息模板。 + /// 属性值。 public static void Debug(string messageTemplate, params object?[] propertyValues) => Logger.Debug(messageTemplate, propertyValues); + /// + /// 写入 Information 级别日志。 + /// + /// 消息模板。 + /// 属性值。 public static void Information(string messageTemplate, params object?[] propertyValues) => Logger.Information(messageTemplate, propertyValues); + /// + /// 写入 Warning 级别日志。 + /// + /// 消息模板。 + /// 属性值。 public static void Warning(string messageTemplate, params object?[] propertyValues) => Logger.Warning(messageTemplate, propertyValues); + /// + /// 写入 Error 级别日志。 + /// + /// 消息模板。 + /// 属性值。 public static void Error(string messageTemplate, params object?[] propertyValues) => Logger.Error(messageTemplate, propertyValues); + /// + /// 写入 Error 级别日志,并附带异常信息。 + /// + /// 异常对象。 + /// 消息模板。 + /// 属性值。 public static void Error(Exception exception, string messageTemplate, params object?[] propertyValues) => Logger.Error(exception, messageTemplate, propertyValues); + /// + /// 写入 Fatal 级别日志。 + /// + /// 消息模板。 + /// 属性值。 public static void Fatal(string messageTemplate, params object?[] propertyValues) => Logger.Fatal(messageTemplate, propertyValues); + /// + /// 写入 Fatal 级别日志,并附带异常信息。 + /// + /// 异常对象。 + /// 消息模板。 + /// 属性值。 public static void Fatal(Exception exception, string messageTemplate, params object?[] propertyValues) => Logger.Fatal(exception, messageTemplate, propertyValues); } diff --git a/Avalonia-EFCore/Database/AppDataContext.cs b/Avalonia-EFCore/Database/AppDataContext.cs index 0fd0426..e8c5f09 100644 --- a/Avalonia-EFCore/Database/AppDataContext.cs +++ b/Avalonia-EFCore/Database/AppDataContext.cs @@ -19,6 +19,10 @@ namespace Avalonia_EFCore.Database /// API refresh token 数据 public DbSet ApiRefreshTokens => Set(); + /// + /// 配置实体映射,包括主键、索引和属性约束。 + /// + /// 模型构建器。 protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); diff --git a/Avalonia-EFCore/Database/AppDataContextFactory.cs b/Avalonia-EFCore/Database/AppDataContextFactory.cs index 1a332c4..84caefa 100644 --- a/Avalonia-EFCore/Database/AppDataContextFactory.cs +++ b/Avalonia-EFCore/Database/AppDataContextFactory.cs @@ -2,8 +2,16 @@ using Microsoft.EntityFrameworkCore.Design; namespace Avalonia_EFCore.Database { + /// + /// 设计时 DbContext 工厂,用于 EF Core 迁移工具生成迁移代码。 + /// public class AppDataContextFactory : IDesignTimeDbContextFactory { + /// + /// 创建用于设计时的 AppDataContext 实例,默认使用 SQLite 提供程序。 + /// + /// 命令行参数。 + /// 配置好的数据上下文实例。 public AppDataContext CreateDbContext(string[] args) { DatabaseProviderRegistry.RegisterDefaults(); diff --git a/Avalonia-EFCore/Database/AppDbContext.cs b/Avalonia-EFCore/Database/AppDbContext.cs index 9a0686e..48d283c 100644 --- a/Avalonia-EFCore/Database/AppDbContext.cs +++ b/Avalonia-EFCore/Database/AppDbContext.cs @@ -12,8 +12,15 @@ namespace Avalonia_EFCore.Database /// public abstract class AppDbContext(DatabaseConfiguration dbConfig) : DbContext { + /// + /// 数据库配置。 + /// private readonly DatabaseConfiguration _dbConfig = dbConfig; + /// + /// 配置数据库提供程序和连接选项。 + /// + /// 选项构建器。 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (optionsBuilder.IsConfigured) return; @@ -65,12 +72,21 @@ namespace Avalonia_EFCore.Database return base.SaveChanges(acceptAllChangesOnSuccess); } + /// + /// 异步保存更改,自动设置时间戳。 + /// + /// 是否在成功时接受所有更改。 + /// 取消令牌。 + /// 受影响的行数。 public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { SetTimestamps(); return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } + /// + /// 自动设置新增或修改实体的 CreatedAt 和 UpdatedAt 时间戳。 + /// private void SetTimestamps() { var entries = ChangeTracker.Entries() diff --git a/Avalonia-EFCore/Database/DatabaseManager.cs b/Avalonia-EFCore/Database/DatabaseManager.cs index de17e18..22f2187 100644 --- a/Avalonia-EFCore/Database/DatabaseManager.cs +++ b/Avalonia-EFCore/Database/DatabaseManager.cs @@ -17,10 +17,25 @@ namespace Avalonia_EFCore.Database /// public class DatabaseManager where TContext : AppDbContext { + /// + /// 数据库上下文实例。 + /// private readonly TContext _context; + /// + /// 数据库配置。 + /// private readonly DatabaseConfiguration _config; + /// + /// DI 服务提供程序(可选,用于种子数据中解析服务)。 + /// private readonly IServiceProvider? _serviceProvider; + /// + /// 初始化数据库管理器。 + /// + /// 数据库上下文。 + /// 数据库配置。 + /// 可选的 DI 容器。 public DatabaseManager(TContext context, DatabaseConfiguration config, IServiceProvider? serviceProvider = null) { _context = context; @@ -128,6 +143,10 @@ namespace Avalonia_EFCore.Database } } + /// + /// 获取当前应用程序的版本号,优先读取 AssemblyInformationalVersion,回退到 AssemblyVersion。 + /// + /// 应用程序版本字符串。 private static string GetApplicationVersion() { var assembly = Assembly.GetEntryAssembly() ?? typeof(TContext).Assembly; @@ -181,10 +200,25 @@ namespace Avalonia_EFCore.Database /// public class DatabaseVersionInfo { + /// + /// 获取或设置数据库提供程序名称。 + /// public string Provider { get; set; } = string.Empty; + /// + /// 获取或设置已应用的迁移列表。 + /// public List AppliedMigrations { get; set; } = new(); + /// + /// 获取或设置待应用的迁移列表。 + /// public List PendingMigrations { get; set; } = new(); + /// + /// 获取或设置是否为最新版本。 + /// public bool IsLatest { get; set; } + /// + /// 获取或设置数据库是否可连接。 + /// public bool CanConnect { get; set; } } } diff --git a/Avalonia-EFCore/Database/DatabaseProviderRegistry.cs b/Avalonia-EFCore/Database/DatabaseProviderRegistry.cs index 11520e0..7cd9bce 100644 --- a/Avalonia-EFCore/Database/DatabaseProviderRegistry.cs +++ b/Avalonia-EFCore/Database/DatabaseProviderRegistry.cs @@ -15,6 +15,9 @@ namespace Avalonia_EFCore.Database /// public delegate void ProviderConfigurator(DbContextOptionsBuilder optionsBuilder, string connectionString, int timeout); + /// + /// 保存已注册的数据库提供程序及其配置委托。 + /// private static readonly Dictionary _providers = new(); /// diff --git a/Avalonia-EFCore/Models/ApiRefreshTokenEntity.cs b/Avalonia-EFCore/Models/ApiRefreshTokenEntity.cs index 97d333d..ef03000 100644 --- a/Avalonia-EFCore/Models/ApiRefreshTokenEntity.cs +++ b/Avalonia-EFCore/Models/ApiRefreshTokenEntity.cs @@ -11,39 +11,69 @@ namespace Avalonia_EFCore.Models [Table("api-refresh-token")] public class ApiRefreshTokenEntity { + /// + /// 获取或设置主键 ID(自增)。 + /// [Key] [Column("id")] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long Id { get; set; } + /// + /// 获取或设置关联的用户 ID。 + /// [Column("user-id")] public int UserId { get; set; } + /// + /// 获取或设置 Token 的 SHA256 哈希值,用于安全存储和查询。 + /// [Column("token-hash")] [MaxLength(128)] public string TokenHash { get; set; } = string.Empty; + /// + /// 获取或设置 Token 创建时间。 + /// [Column("created-at")] public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + /// + /// 获取或设置 Token 过期时间。 + /// [Column("expires-at")] public DateTime ExpiresAt { get; set; } + /// + /// 获取或设置 Token 撤销时间,null 表示尚未撤销。 + /// [Column("revoked-at")] public DateTime? RevokedAt { get; set; } + /// + /// 获取或设置替换此 Token 的新 Token 哈希值(轮换时设置)。 + /// [Column("replaced-by-token-hash")] [MaxLength(128)] public string? ReplacedByTokenHash { get; set; } + /// + /// 获取或设置创建设备标识(如 User-Agent)。 + /// [Column("device")] [MaxLength(200)] public string? Device { get; set; } + /// + /// 获取或设置创建时的客户端 IP 地址。 + /// [Column("ip-address")] [MaxLength(64)] public string? IpAddress { get; set; } + /// + /// 获取 Token 是否有效(未被撤销且未过期)。 + /// public bool IsActive => RevokedAt is null && ExpiresAt > DateTime.UtcNow; } } diff --git a/Avalonia-EFCore/Models/UserEntity.cs b/Avalonia-EFCore/Models/UserEntity.cs index 284511d..d6da474 100644 --- a/Avalonia-EFCore/Models/UserEntity.cs +++ b/Avalonia-EFCore/Models/UserEntity.cs @@ -11,31 +11,49 @@ namespace Avalonia_EFCore.Models [Table("user")] public class UserEntity { + /// + /// 获取或设置用户主键 ID(自增)。 + /// [Key] [Comment("用户主键")] [Column("id")] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } + /// + /// 获取或设置用户名称。 + /// [Comment("用户名称")] [Column("name")] [MaxLength(100)] public string? Name { get; set; } + /// + /// 获取或设置用户邮箱。 + /// [Comment("用户邮箱")] [Column("email")] [MaxLength(200)] public string? Email { get; set; } + /// + /// 获取或设置用户电话号码。 + /// [Comment("电话号码")] [Column("phone-number")] [MaxLength(50)] public string? PhoneNumber { get; set; } + /// + /// 获取或设置用户创建时间。 + /// [Comment("创建时间")] [Column("created-at")] public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + /// + /// 获取或设置用户最后更新时间。 + /// [Comment("更新时间")] [Column("updated-at")] public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; diff --git a/Avalonia-EFCore/Models/WeatherForecast.cs b/Avalonia-EFCore/Models/WeatherForecast.cs index 85a17e7..c2547b0 100644 --- a/Avalonia-EFCore/Models/WeatherForecast.cs +++ b/Avalonia-EFCore/Models/WeatherForecast.cs @@ -1,13 +1,28 @@ namespace Avalonia_EFCore.Models { + /// + /// 天气预报数据模型(内存/DTO 用,非数据库实体)。 + /// public class WeatherForecast { + /// + /// 获取或设置预报日期。 + /// public DateOnly Date { get; set; } + /// + /// 获取或设置摄氏温度。 + /// public int TemperatureC { get; set; } + /// + /// 获取华氏温度(根据摄氏温度自动计算)。 + /// public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + /// + /// 获取或设置天气摘要。 + /// public string? Summary { get; set; } } } diff --git a/Avalonia-EFCore/Models/WeatherForecastEntity.cs b/Avalonia-EFCore/Models/WeatherForecastEntity.cs index 0ff5e26..c5a3772 100644 --- a/Avalonia-EFCore/Models/WeatherForecastEntity.cs +++ b/Avalonia-EFCore/Models/WeatherForecastEntity.cs @@ -11,29 +11,47 @@ namespace Avalonia_EFCore.Models [Table("weather-forecast")] public class WeatherForecastEntity { + /// + /// 获取或设置天气预报主键 ID(自增)。 + /// [Key] [Comment("天气预报主键")] [Column("id")] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } + /// + /// 获取或设置预报日期。 + /// [Comment("预报日期")] [Column("date")] public DateOnly Date { get; set; } + /// + /// 获取或设置摄氏温度。 + /// [Comment("摄氏温度")] [Column("temperature-c")] public int TemperatureC { get; set; } + /// + /// 获取或设置天气摘要。 + /// [Comment("天气摘要")] [Column("summary")] [MaxLength(200)] public string? Summary { get; set; } + /// + /// 获取或设置记录创建时间。 + /// [Comment("创建时间")] [Column("created-at")] public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + /// + /// 获取或设置记录最后更新时间。 + /// [Comment("更新时间")] [Column("updated-at")] public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; diff --git a/Avalonia-PC/App.axaml.cs b/Avalonia-PC/App.axaml.cs index 1bf69a4..8c1845b 100644 --- a/Avalonia-PC/App.axaml.cs +++ b/Avalonia-PC/App.axaml.cs @@ -7,13 +7,22 @@ using Microsoft.Extensions.DependencyInjection; namespace Avalonia_PC { + /// + /// Avalonia 应用程序入口类,负责初始化 XAML 资源和设置主窗口。 + /// public partial class App : Application { + /// + /// 加载 Avalonia XAML 资源。 + /// public override void Initialize() { AvaloniaXamlLoader.Load(this); } + /// + /// 框架初始化完成后设置主窗口和数据上下文。 + /// public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) diff --git a/Avalonia-PC/Authentication/DefaultPcThirdPartyAuthorizationClient.cs b/Avalonia-PC/Authentication/DefaultPcThirdPartyAuthorizationClient.cs index d4f6285..0ff5ff4 100644 --- a/Avalonia-PC/Authentication/DefaultPcThirdPartyAuthorizationClient.cs +++ b/Avalonia-PC/Authentication/DefaultPcThirdPartyAuthorizationClient.cs @@ -10,6 +10,12 @@ namespace Avalonia_PC.Authentication /// public sealed class DefaultPcThirdPartyAuthorizationClient : IPcThirdPartyAuthorizationClient { + /// + /// 验证第三方授权码是否有效。默认实现将 "invalid" 视为授权丢失,其余视为有效。 + /// + /// 第三方授权码。 + /// 取消令牌。 + /// 授权检查结果。 public Task ValidateAuthorizationCodeAsync( string authorizationCode, CancellationToken cancellationToken = default) @@ -23,6 +29,12 @@ namespace Avalonia_PC.Authentication return Task.FromResult(ThirdPartyAuthCheckResult.Valid); } + /// + /// 刷新第三方授权。默认实现总是返回 TemporaryFailure,表示暂时无法刷新。 + /// + /// 授权引用标识。 + /// 取消令牌。 + /// 授权检查结果。 public Task RefreshAuthorizationAsync( string authorizationReference, CancellationToken cancellationToken = default) diff --git a/Avalonia-PC/Authentication/PcAuthEndpointService.cs b/Avalonia-PC/Authentication/PcAuthEndpointService.cs index cff1dbf..270b01b 100644 --- a/Avalonia-PC/Authentication/PcAuthEndpointService.cs +++ b/Avalonia-PC/Authentication/PcAuthEndpointService.cs @@ -8,6 +8,10 @@ using System.Threading.Tasks; namespace Avalonia_PC.Authentication { + /// + /// PC 端鉴权端点服务,实现 , + /// 处理授权码登录、Token 刷新和登出操作。 + /// public sealed class PcAuthEndpointService(PcGlobalTokenService tokenService) : IPcAuthEndpointService { private static readonly JsonSerializerOptions JsonOptions = new() @@ -15,6 +19,7 @@ namespace Avalonia_PC.Authentication PropertyNameCaseInsensitive = true, }; + /// public async Task AuthorizeAsync(ServiceEndpointContext ctx) { var request = Deserialize(ctx.Body); @@ -28,6 +33,7 @@ namespace Avalonia_PC.Authentication return ResponseHelper.Ok(token, "授权成功"); } + /// public async Task RefreshAsync(ServiceEndpointContext ctx) { var request = Deserialize(ctx.Body); @@ -42,6 +48,7 @@ namespace Avalonia_PC.Authentication return ResponseHelper.Ok(refreshed, "刷新成功"); } + /// public Task LogoutAsync(ServiceEndpointContext ctx) { var request = Deserialize(ctx.Body); @@ -50,6 +57,12 @@ namespace Avalonia_PC.Authentication return Task.FromResult(ResponseHelper.Succeed("退出成功")); } + /// + /// 将 JSON 请求体反序列化为指定类型。 + /// + /// 目标类型。 + /// JSON 请求体字符串,可为空。 + /// 反序列化后的对象;若 body 为空则返回默认值。 private static T? Deserialize(string? body) { return string.IsNullOrWhiteSpace(body) @@ -57,6 +70,11 @@ namespace Avalonia_PC.Authentication : JsonSerializer.Deserialize(body, JsonOptions); } + /// + /// 从 Authorization 头中提取 Bearer Token。 + /// + /// Authorization 头的值。 + /// 提取的 Token 字符串;若无法提取则返回 null。 private static string? ExtractBearerToken(string? authorization) { if (string.IsNullOrWhiteSpace(authorization)) @@ -64,7 +82,10 @@ namespace Avalonia_PC.Authentication return null; } - const string prefix = "Bearer "; + /// + /// Bearer Token 的前缀常量。 + /// + const string prefix = "Bearer "; return authorization.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) ? authorization[prefix.Length..].Trim() : authorization.Trim(); diff --git a/Avalonia-PC/Authentication/PcAuthService.cs b/Avalonia-PC/Authentication/PcAuthService.cs index 8ffed2a..0624539 100644 --- a/Avalonia-PC/Authentication/PcAuthService.cs +++ b/Avalonia-PC/Authentication/PcAuthService.cs @@ -6,8 +6,12 @@ using System.Threading.Tasks; namespace Avalonia_PC.Authentication { + /// + /// PC 端鉴权服务,基于全局 Token 验证用户身份,实现 。 + /// public sealed class PcAuthService(PcGlobalTokenService tokenService) : IAuthService { + /// public async Task AuthenticateAsync(ServiceEndpointContext context) { var token = ExtractBearerToken(context.GetHeader("Authorization")); @@ -29,11 +33,17 @@ namespace Avalonia_PC.Authentication return new ClaimsPrincipal(identity); } + /// public Task AuthorizeAsync(ClaimsPrincipal user, string policy) { return Task.FromResult(user.Identity?.IsAuthenticated == true); } + /// + /// 从 Authorization 头中提取 Bearer Token。 + /// + /// Authorization 头的值。 + /// 提取的 Token 字符串;若无法提取则返回 null。 private static string? ExtractBearerToken(string? authorization) { if (string.IsNullOrWhiteSpace(authorization)) @@ -41,7 +51,7 @@ namespace Avalonia_PC.Authentication return null; } - const string prefix = "Bearer "; + string prefix = "Bearer "; return authorization.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) ? authorization[prefix.Length..].Trim() : authorization.Trim(); diff --git a/Avalonia-PC/Authentication/PcGlobalTokenService.cs b/Avalonia-PC/Authentication/PcGlobalTokenService.cs index cd06d2a..27e8bb8 100644 --- a/Avalonia-PC/Authentication/PcGlobalTokenService.cs +++ b/Avalonia-PC/Authentication/PcGlobalTokenService.cs @@ -7,16 +7,45 @@ using System.Threading.Tasks; namespace Authentication { + /// + /// PC 端全局 Token 服务,管理全局唯一的访问 Token。 + /// 支持授权码登录、Token 刷新、有效性验证和登出, + /// 在第三方授权暂时失败时使用缩短有效期的临时 Token。 + /// public sealed class PcGlobalTokenService(IPcThirdPartyAuthorizationClient thirdPartyClient) { + /// + /// 超级管理员角色集合。 + /// private static readonly string[] SuperRoles = ["SuperAdmin", "Admin"]; + /// + /// 线程同步锁。 + /// private readonly object _syncRoot = new(); + /// + /// 当前 Token 状态。 + /// private PcTokenState? _current; + /// + /// 正常 Token 有效期(8 小时)。 + /// private static readonly TimeSpan NormalLifetime = TimeSpan.FromHours(8); + /// + /// 第三方暂时失败时的 Token 有效期(20 分钟)。 + /// private static readonly TimeSpan TemporaryFailureLifetime = TimeSpan.FromMinutes(20); + /// + /// 第三方暂时失败的最长容忍窗口(24 小时),超出后清除 Token。 + /// private static readonly TimeSpan MaxTemporaryFailureWindow = TimeSpan.FromHours(24); + /// + /// 使用授权码进行登录授权,验证成功后颁发全局 Token。 + /// + /// 第三方授权码。 + /// 取消令牌。 + /// Token 响应;若授权码无效则返回 null。 public async Task AuthorizeAsync(string? authorizationCode, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(authorizationCode)) @@ -33,6 +62,13 @@ namespace Authentication return IssueToken(authorizationCode, NormalLifetime, resetTemporaryFailureWindow: true); } + /// + /// 刷新当前 Token,向第三方验证授权引用是否仍然有效。 + /// 根据第三方返回结果决定是续期、降级为临时 Token 还是清除。 + /// + /// 当前 Token。 + /// 取消令牌。 + /// 新的 Token 响应;若授权丢失则返回 null。 public async Task RefreshAsync(string? token, CancellationToken cancellationToken = default) { PcTokenState? current; @@ -56,6 +92,12 @@ namespace Authentication }; } + /// + /// 验证 Token 是否有效,若已过期则尝试自动刷新。 + /// + /// 要验证的 Token。 + /// 取消令牌。 + /// Token 是否有效。 public async Task ValidateAsync(string? token, CancellationToken cancellationToken = default) { PcTokenState? current; @@ -76,6 +118,10 @@ namespace Authentication return await RefreshAsync(token, cancellationToken) is not null; } + /// + /// 登出并清除当前 Token。 + /// + /// 要清除的 Token。 public void Logout(string? token) { lock (_syncRoot) @@ -87,6 +133,13 @@ namespace Authentication } } + /// + /// 颁发新的全局 Token。 + /// + /// 授权引用标识。 + /// Token 有效期。 + /// 是否重置暂时失败窗口。 + /// 包含 Token 和过期时间的响应。 private PcTokenResponse IssueToken(string authorizationReference, TimeSpan lifetime, bool resetTemporaryFailureWindow) { var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)); @@ -105,6 +158,11 @@ namespace Authentication return new PcTokenResponse(token, state.ExpiresAt, SuperRoles); } + /// + /// 在第三方暂时失败时刷新 Token。若超出最大容忍窗口则清除 Token。 + /// + /// 当前 Token 状态。 + /// 新的临时 Token 响应;若超出容忍窗口则返回 null。 private PcTokenResponse? RefreshAfterTemporaryFailure(PcTokenState current) { var startedAt = current.TemporaryFailureStartedAt ?? DateTime.UtcNow; @@ -116,6 +174,10 @@ namespace Authentication return IssueToken(current.AuthorizationReference, TemporaryFailureLifetime, resetTemporaryFailureWindow: false); } + /// + /// 清除当前 Token 并返回 null。 + /// + /// 始终返回 null。 private PcTokenResponse? ClearAndReturnNull() { lock (_syncRoot) @@ -126,6 +188,11 @@ namespace Authentication return null; } + /// + /// 检查给定 Token 是否与当前持有的 Token 匹配。 + /// + /// 要检查的 Token。 + /// 是否匹配。 private bool IsCurrentToken(string? token) { return !string.IsNullOrWhiteSpace(token) && @@ -133,12 +200,24 @@ namespace Authentication string.Equals(_current.TokenHash, HashToken(token), StringComparison.Ordinal); } + /// + /// 对 Token 原文进行 SHA256 哈希,返回十六进制字符串。 + /// + /// Token 原文。 + /// SHA256 哈希后的十六进制字符串。 private static string HashToken(string token) { var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(token)); return Convert.ToHexString(bytes); } + /// + /// 保存当前全局 Token 的内部状态。 + /// + /// Token 的 SHA256 哈希值。 + /// 授权引用标识。 + /// 过期时间。 + /// 第三方暂时失败的起始时间。 private sealed record PcTokenState( string TokenHash, string AuthorizationReference, diff --git a/Avalonia-PC/Program.cs b/Avalonia-PC/Program.cs index 9744f18..4735cba 100644 --- a/Avalonia-PC/Program.cs +++ b/Avalonia-PC/Program.cs @@ -14,10 +14,20 @@ using System; namespace Avalonia_PC { + /// + /// 桌面应用程序入口类,负责配置 DI 容器、初始化数据库和启动 Avalonia 框架。 + /// internal sealed class Program { + /// + /// 获取全局 DI 服务提供程序。 + /// public static IServiceProvider Services { get; private set; } = null!; + /// + /// 应用程序主入口点。 + /// + /// 命令行参数。 [STAThread] public static void Main(string[] args) { @@ -40,6 +50,9 @@ namespace Avalonia_PC BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); } + /// + /// 配置 DI 容器,注册数据库、业务服务、鉴权服务和统一端点。 + /// private static void ConfigureServices() { var services = new ServiceCollection(); @@ -71,7 +84,10 @@ namespace Avalonia_PC Services = services.BuildServiceProvider(); } - // Avalonia configuration, don't remove; also used by visual designer. + /// + /// 构建 Avalonia 应用程序(供可视化设计器使用,请勿删除)。 + /// + /// Avalonia 应用构建器。 public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() diff --git a/Avalonia-PC/ViewLocator.cs b/Avalonia-PC/ViewLocator.cs index 484d1fe..dfc998d 100644 --- a/Avalonia-PC/ViewLocator.cs +++ b/Avalonia-PC/ViewLocator.cs @@ -12,8 +12,18 @@ namespace Avalonia_PC [RequiresUnreferencedCode( "Default implementation of ViewLocator involves reflection which may be trimmed away.", Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")] + /// + /// 视图定位器,根据 ViewModel 类型自动查找对应的 View, + /// 实现 IDataTemplate 以支持 Avalonia 的数据模板机制。 + /// public class ViewLocator : IDataTemplate { + /// + /// 根据 ViewModel 实例构建对应的 View 控件。 + /// 约定:将 ViewModels 命名空间中的 ViewModel 替换为 Views 命名空间中的同名 View。 + /// + /// ViewModel 实例。 + /// 对应的 View 控件;若未找到则返回 TextBlock 显示错误信息。 public Control? Build(object? param) { if (param is null) @@ -30,6 +40,11 @@ namespace Avalonia_PC return new TextBlock { Text = "Not Found: " + name }; } + /// + /// 判断数据对象是否为 ViewModel 类型(以 "ViewModel" 结尾)。 + /// + /// 要判断的数据对象。 + /// 是否为 ViewModel。 public bool Match(object? data) { return data is ViewModelBase; diff --git a/Avalonia-PC/ViewModels/MainWindowViewModel.cs b/Avalonia-PC/ViewModels/MainWindowViewModel.cs index 812e4a5..50bf8e5 100644 --- a/Avalonia-PC/ViewModels/MainWindowViewModel.cs +++ b/Avalonia-PC/ViewModels/MainWindowViewModel.cs @@ -1,7 +1,13 @@ namespace Avalonia_PC.ViewModels { + /// + /// 主窗口的 ViewModel,提供问候语等绑定属性。 + /// public partial class MainWindowViewModel : ViewModelBase { + /// + /// 获取问候语文本。 + /// public string Greeting { get; } = "Welcome to Avalonia!"; } } diff --git a/Avalonia-PC/ViewModels/ViewModelBase.cs b/Avalonia-PC/ViewModels/ViewModelBase.cs index 46985b7..e1414cd 100644 --- a/Avalonia-PC/ViewModels/ViewModelBase.cs +++ b/Avalonia-PC/ViewModels/ViewModelBase.cs @@ -2,6 +2,10 @@ namespace Avalonia_PC.ViewModels { + /// + /// ViewModel 基类,继承自 CommunityToolkit.Mvvm 的 ObservableObject, + /// 提供属性变更通知功能。 + /// public abstract class ViewModelBase : ObservableObject { } diff --git a/Avalonia-PC/Views/MainWindow.BridgeScript.cs b/Avalonia-PC/Views/MainWindow.BridgeScript.cs index c8f35ff..41210d3 100644 --- a/Avalonia-PC/Views/MainWindow.BridgeScript.cs +++ b/Avalonia-PC/Views/MainWindow.BridgeScript.cs @@ -1,5 +1,8 @@ namespace Avalonia_PC.Views { + /// + /// MainWindow 的分部类,定义注入 WebView2 的 JavaScript Bridge 脚本。 + /// public partial class MainWindow { private const string BridgeScript = """ @@ -106,6 +109,9 @@ if (!window.__appBridgeInstalled) { }); }; + /// + /// WebView2 Bridge 中的 XMLHttpRequest 替代实现,将 app:// 请求拦截并转为 C# 调用。 + /// class BridgeXMLHttpRequest { constructor() { this._native = new NativeXMLHttpRequest(); diff --git a/Avalonia-PC/Views/MainWindow.Routes.cs b/Avalonia-PC/Views/MainWindow.Routes.cs index 984d04e..856c3fd 100644 --- a/Avalonia-PC/Views/MainWindow.Routes.cs +++ b/Avalonia-PC/Views/MainWindow.Routes.cs @@ -9,6 +9,9 @@ using System.Threading.Tasks; namespace Avalonia_PC.Views { + /// + /// MainWindow 的分部类,负责路由注册和统一端点适配。 + /// public partial class MainWindow { /// @@ -22,6 +25,9 @@ namespace Avalonia_PC.Views /// private IServiceProvider _services = null!; + /// + /// 从 DI 获取统一端点集合并构建桌面适配器。 + /// private void RegisterRoutes() { // 从 DI 获取已构建的端点集合 diff --git a/Avalonia-PC/Views/MainWindow.axaml.cs b/Avalonia-PC/Views/MainWindow.axaml.cs index 7030598..88cd6dd 100644 --- a/Avalonia-PC/Views/MainWindow.axaml.cs +++ b/Avalonia-PC/Views/MainWindow.axaml.cs @@ -12,23 +12,56 @@ using System.Threading.Tasks; namespace Avalonia_PC.Views { + /// + /// 主窗口,承载 WebView2 控件并管理前后端 Bridge 通信。 + /// public partial class MainWindow : Window { + /// + /// 自定义协议方案名称。 + /// private const string AppScheme = "app"; + /// + /// 在线模式下的前端启动 URL。 + /// private const string? OnlineStartupUrl = "http://localhost:51240"; //private const string? OnlineStartupUrl = null; + /// + /// 离线模式下的前端本地文件路径,为空则使用在线模式。 + /// private const string? LocalStartupPath = null; private static readonly JsonSerializerOptions BridgeJsonSerializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; + /// + /// WebView2 原生控件实例。 + /// private NativeWebView? _webView; + /// + /// 标记 WebView 事件是否已绑定。 + /// private bool _eventsAttached; + /// + /// WebView 适配器对象。 + /// private object? _webViewAdapter; + /// + /// 本地 HTTP 服务器实例(离线模式)。 + /// private HttpListener? _localHttpServer; + /// + /// 本地 HTTP 服务器的取消令牌源。 + /// private CancellationTokenSource? _localHttpServerCts; + /// + /// 本地 HTTP 服务器的基础 URL。 + /// private string? _localHttpBaseUrl; + /// + /// 本地 HTTP 服务器的根目录路径。 + /// private string? _localHttpRoot; #region 生命周期与 WebView 事件 @@ -610,18 +643,39 @@ namespace Avalonia_PC.Views #region DTO / 路由上下文模型 + /// + /// Bridge 通信响应 DTO,用于序列化返回给前端的数据。 + /// private sealed class AppResponse { + /// + /// 获取或设置响应类型标识。 + /// public string Kind { get; set; } = string.Empty; + /// + /// 获取或设置请求 ID(对应前端请求)。 + /// public string? Id { get; set; } + /// + /// 获取或设置 HTTP 状态码。 + /// public int StatusCode { get; set; } + /// + /// 获取或设置状态描述文本。 + /// public string StatusMessage { get; set; } = string.Empty; + /// + /// 获取或设置响应体 JSON 字符串。 + /// public string Body { get; set; } = string.Empty; + /// + /// 获取或设置响应头字典。 + /// public Dictionary Headers { get; set; } = new(); } diff --git a/Avalonia-Services/Core/GlobalExceptionFilter.cs b/Avalonia-Services/Core/GlobalExceptionFilter.cs index 25d4ba7..a8f0825 100644 --- a/Avalonia-Services/Core/GlobalExceptionFilter.cs +++ b/Avalonia-Services/Core/GlobalExceptionFilter.cs @@ -10,9 +10,13 @@ namespace Avalonia_Services.Core /// public sealed class GlobalExceptionFilter : IEndpointFilter { + /// + /// 是否在错误响应中包含异常详情。 + /// private readonly bool _includeDetails; /// + /// 初始化全局异常过滤器。 /// /// 是否在响应中包含异常详情(开发环境建议 true,生产环境 false) public GlobalExceptionFilter(bool includeDetails = false) @@ -20,6 +24,11 @@ namespace Avalonia_Services.Core _includeDetails = includeDetails; } + /// + /// 执行过滤器逻辑:包裹下一个委托,捕获所有未处理异常并转换为统一错误响应。 + /// + /// 请求上下文。 + /// 管道中的下一个委托。 public async Task InvokeAsync(ServiceEndpointContext context, EndpointFilterDelegate next) { try @@ -73,6 +82,11 @@ namespace Avalonia_Services.Core } } + /// + /// 记录异常日志,优先使用 Serilog,不可用时回退到 Console。 + /// + /// 请求上下文。 + /// 异常对象。 private static void LogException(ServiceEndpointContext context, Exception ex) { try diff --git a/Avalonia-Services/Core/IAuthService.cs b/Avalonia-Services/Core/IAuthService.cs index 236ea74..2569b67 100644 --- a/Avalonia-Services/Core/IAuthService.cs +++ b/Avalonia-Services/Core/IAuthService.cs @@ -23,13 +23,13 @@ namespace Avalonia_Services.Core /// public sealed class AnonymousAuthService : IAuthService { + /// public Task AuthenticateAsync(ServiceEndpointContext context) { // 匿名用户,始终通过 var identity = new ClaimsIdentity("anonymous"); return Task.FromResult(new ClaimsPrincipal(identity)); } - /// public Task AuthorizeAsync(ClaimsPrincipal user, string policy) { diff --git a/Avalonia-Services/Core/IEndpointFilter.cs b/Avalonia-Services/Core/IEndpointFilter.cs index 776f81b..81c4920 100644 --- a/Avalonia-Services/Core/IEndpointFilter.cs +++ b/Avalonia-Services/Core/IEndpointFilter.cs @@ -25,13 +25,21 @@ namespace Avalonia_Services.Core /// internal sealed class AnonymousEndpointFilter : IEndpointFilter { + /// + /// 匿名过滤器的委托实现。 + /// private readonly Func _filter; + /// + /// 使用匿名函数创建过滤器。 + /// + /// 过滤器委托。 public AnonymousEndpointFilter(Func filter) { _filter = filter; } + /// public Task InvokeAsync(ServiceEndpointContext context, EndpointFilterDelegate next) { return _filter(context, next); diff --git a/Avalonia-Services/Core/ServiceEndpointCollection.cs b/Avalonia-Services/Core/ServiceEndpointCollection.cs index 89f4743..170d19e 100644 --- a/Avalonia-Services/Core/ServiceEndpointCollection.cs +++ b/Avalonia-Services/Core/ServiceEndpointCollection.cs @@ -5,11 +5,17 @@ using Microsoft.Extensions.DependencyInjection; namespace Avalonia_Services.Core { + /// + /// 端点挂载的宿主目标。 + /// [Flags] public enum EndpointHostTarget { + /// 挂载到 Avalonia-API(ASP.NET Core Web API)。 Api = 1, + /// 挂载到 Avalonia-PC(桌面 WebView)。 Pc = 2, + /// 同时挂载到 API 和 PC。 All = Api | Pc, } @@ -69,6 +75,15 @@ namespace Avalonia_Services.Core return this; } + /// + /// 设置端点的 OpenAPI 元数据(标签、摘要、描述、请求/响应类型)。 + /// + /// OpenAPI 分组标签。 + /// 简要摘要。 + /// 详细描述。 + /// 请求体类型。 + /// 成功响应类型。 + /// 当前端点实例(Fluent API)。 public ServiceEndpoint WithOpenApi( string tag, string summary, @@ -122,6 +137,11 @@ namespace Avalonia_Services.Core return this; } + /// + /// 判断端点是否支持指定的宿主目标。 + /// + /// 要检查的宿主目标。 + /// 是否支持。 public bool SupportsHost(EndpointHostTarget host) { return (HostTarget & host) != 0; @@ -136,6 +156,11 @@ namespace Avalonia_Services.Core /// 所有已注册的端点 public List Endpoints { get; } = new(); + /// + /// 获取指定宿主目标的所有端点。 + /// + /// 宿主目标。 + /// 匹配的端点集合。 public IEnumerable ForHost(EndpointHostTarget host) { return Endpoints.Where(endpoint => endpoint.SupportsHost(host)); @@ -152,6 +177,13 @@ namespace Avalonia_Services.Core return AddEndpoint(pattern, "GET", handler); } + /// + /// 注册一个带服务依赖注入的 GET 端点。 + /// + /// 服务类型。 + /// 路由路径。 + /// 接受服务实例和上下文的处理器。 + /// 已注册的端点实例。 public ServiceEndpoint MapGet( string pattern, Func> handler) @@ -168,6 +200,13 @@ namespace Avalonia_Services.Core return AddEndpoint(pattern, "POST", handler); } + /// + /// 注册一个带服务依赖注入的 POST 端点。 + /// + /// 服务类型。 + /// 路由路径。 + /// 接受服务实例和上下文的处理器。 + /// 已注册的端点实例。 public ServiceEndpoint MapPost( string pattern, Func> handler) @@ -184,6 +223,13 @@ namespace Avalonia_Services.Core return AddEndpoint(pattern, "PUT", handler); } + /// + /// 注册一个带服务依赖注入的 PUT 端点。 + /// + /// 服务类型。 + /// 路由路径。 + /// 接受服务实例和上下文的处理器。 + /// 已注册的端点实例。 public ServiceEndpoint MapPut( string pattern, Func> handler) @@ -200,6 +246,13 @@ namespace Avalonia_Services.Core return AddEndpoint(pattern, "DELETE", handler); } + /// + /// 注册一个带服务依赖注入的 DELETE 端点。 + /// + /// 服务类型。 + /// 路由路径。 + /// 接受服务实例和上下文的处理器。 + /// 已注册的端点实例。 public ServiceEndpoint MapDelete( string pattern, Func> handler) @@ -226,6 +279,13 @@ namespace Avalonia_Services.Core return this; } + /// + /// 内部方法,创建端点并添加到集合。 + /// + /// 路由路径。 + /// HTTP 方法。 + /// 端点处理器。 + /// 已创建的端点实例。 private ServiceEndpoint AddEndpoint(string pattern, string method, Func> handler) { var endpoint = new ServiceEndpoint @@ -238,6 +298,12 @@ namespace Avalonia_Services.Core return endpoint; } + /// + /// 创建自动从 DI 解析服务实例并调用处理器的委托包装。 + /// + /// 服务类型。 + /// 接受服务实例和上下文的处理器。 + /// 包装后的处理器委托。 private static Func> CreateServiceHandler( Func> handler) where TService : notnull diff --git a/Avalonia-Services/Endpoints/AppEndpoints.cs b/Avalonia-Services/Endpoints/AppEndpoints.cs index c0bb2ef..f2e9dee 100644 --- a/Avalonia-Services/Endpoints/AppEndpoints.cs +++ b/Avalonia-Services/Endpoints/AppEndpoints.cs @@ -89,6 +89,11 @@ namespace Avalonia_Services.Endpoints return ResponseHelper.Ok(forecasts, "获取天气预报成功(内存生成)"); } + /// + /// 从数据库获取用户信息(演示数据库查询),若无数据则返回演示用户。 + /// + /// 服务端点上下文。 + /// 用户信息。 private static async Task GetUserFromDatabaseAsync(ServiceEndpointContext ctx) { var sp = ctx.Items["ServiceProvider"] as IServiceProvider; @@ -109,6 +114,11 @@ namespace Avalonia_Services.Endpoints return ResponseHelper.Ok(user); } + /// + /// 处理前端发送的数据(POST 演示),将数据存入数据库或转为大写返回。 + /// + /// 服务端点上下文。 + /// 处理结果。 private static async Task ProcessDataAsync(ServiceEndpointContext ctx) { var sp = ctx.Items["ServiceProvider"] as IServiceProvider; diff --git a/Avalonia-Services/Endpoints/AuthEndpoints.cs b/Avalonia-Services/Endpoints/AuthEndpoints.cs index bed977a..37d2159 100644 --- a/Avalonia-Services/Endpoints/AuthEndpoints.cs +++ b/Avalonia-Services/Endpoints/AuthEndpoints.cs @@ -8,6 +8,10 @@ namespace Avalonia_Services.Endpoints /// public static class AuthEndpoints { + /// + /// 配置 API 端鉴权端点(登录、刷新、登出)。 + /// + /// 端点构建器。 public static void ConfigureApi(ServiceEndpointBuilder builder) { builder.ConfigureEndpoints(endpoints => @@ -29,6 +33,10 @@ namespace Avalonia_Services.Endpoints }); } + /// + /// 配置 PC 端鉴权端点(授权码登录、刷新、登出)。 + /// + /// 端点构建器。 public static void ConfigurePc(ServiceEndpointBuilder builder) { builder.ConfigureEndpoints(endpoints => diff --git a/Avalonia-Services/Extensions/DesktopEndpointAdapter.cs b/Avalonia-Services/Extensions/DesktopEndpointAdapter.cs index 0aa6ec9..edb4291 100644 --- a/Avalonia-Services/Extensions/DesktopEndpointAdapter.cs +++ b/Avalonia-Services/Extensions/DesktopEndpointAdapter.cs @@ -8,8 +8,17 @@ namespace Avalonia_Services.Extensions /// public class DesktopEndpointAdapter { + /// + /// 统一端点集合。 + /// private readonly ServiceEndpointCollection _endpoints; + /// + /// 鉴权服务。 + /// private readonly IAuthService _authService; + /// + /// DI 服务提供程序。 + /// private readonly IServiceProvider _serviceProvider; /// @@ -17,12 +26,33 @@ namespace Avalonia_Services.Extensions /// public class RouteResult { + /// + /// 获取是否匹配到路由。 + /// public bool IsMatched { get; init; } + /// + /// 获取 HTTP 状态码。 + /// public int StatusCode { get; init; } = 200; - public string StatusMessage { get; init; } = "OK"; + /// + /// 获取状态描述文本。 + /// + public string StatusMessage { get; init; } = ""; + /// + /// 获取响应数据。 + /// public object? Data { get; init; } + /// + /// 获取响应头字典。 + /// public Dictionary ResponseHeaders { get; init; } = new(); + /// + /// 创建成功响应结果。 + /// + /// 响应数据。 + /// 端点上下文。 + /// 路由结果。 public static RouteResult Success(object? data, ServiceEndpointContext ctx) { return new RouteResult @@ -35,6 +65,10 @@ namespace Avalonia_Services.Extensions }; } + /// + /// 创建 404 未找到响应。 + /// + /// 表示未匹配的路由结果。 public static RouteResult NotFound() => new() { IsMatched = false, @@ -43,6 +77,12 @@ namespace Avalonia_Services.Extensions }; } + /// + /// 初始化桌面端点适配器。 + /// + /// 端点集合。 + /// 鉴权服务。 + /// DI 服务提供程序。 public DesktopEndpointAdapter( ServiceEndpointCollection endpoints, IAuthService authService, diff --git a/Avalonia-Services/Services/AuthService/AuthContracts.cs b/Avalonia-Services/Services/AuthService/AuthContracts.cs index 906bb56..5e7abe5 100644 --- a/Avalonia-Services/Services/AuthService/AuthContracts.cs +++ b/Avalonia-Services/Services/AuthService/AuthContracts.cs @@ -1,11 +1,33 @@ namespace Avalonia_Services.Services.AuthService { + /// + /// API 登录请求。 + /// + /// 账号(邮箱或用户名)。 + /// 密码。 + /// 请求的角色列表。 public sealed record ApiLoginRequest(string? Account, string? Password, string[]? Roles = null); + /// + /// API Refresh Token 请求。 + /// + /// 刷新令牌。 public sealed record ApiRefreshTokenRequest(string? RefreshToken); + /// + /// API 登出请求。 + /// + /// 要撤销的刷新令牌。 public sealed record ApiLogoutRequest(string? RefreshToken); + /// + /// 认证 Token 响应,包含 Access Token 和 Refresh Token 及其过期时间。 + /// + /// 访问令牌。 + /// 刷新令牌。 + /// 访问令牌过期时间。 + /// 刷新令牌过期时间。 + /// 用户角色列表。 public sealed record AuthTokenResponse( string AccessToken, string RefreshToken, @@ -13,25 +35,64 @@ namespace Avalonia_Services.Services.AuthService DateTime RefreshTokenExpiresAt, string[] Roles); + /// + /// PC 端授权码登录请求。 + /// + /// 第三方授权码。 public sealed record PcAuthorizeRequest(string? AuthorizationCode); + /// + /// PC 端 Token 刷新请求。 + /// + /// 当前 Token。 public sealed record PcRefreshRequest(string? Token); + /// + /// PC 端登出请求。 + /// + /// 要清除的 Token。 public sealed record PcLogoutRequest(string? Token); + /// + /// PC 端 Token 响应。 + /// + /// 访问令牌。 + /// 过期时间。 + /// 用户角色列表。 public sealed record PcTokenResponse(string Token, DateTime ExpiresAt, string[] Roles); + /// + /// 第三方授权检查结果。 + /// public enum ThirdPartyAuthCheckResult { + /// 授权有效。 Valid, + /// 授权已丢失。 AuthorizationLost, + /// 暂时性失败。 TemporaryFailure, } + /// + /// 第三方授权客户端接口,用于验证和刷新第三方授权。 + /// public interface IPcThirdPartyAuthorizationClient { + /// + /// 验证第三方授权码是否有效。 + /// + /// 第三方授权码。 + /// 取消令牌。 + /// 授权检查结果。 Task ValidateAuthorizationCodeAsync(string authorizationCode, CancellationToken cancellationToken = default); + /// + /// 刷新第三方授权。 + /// + /// 授权引用标识。 + /// 取消令牌。 + /// 授权检查结果。 Task RefreshAuthorizationAsync(string authorizationReference, CancellationToken cancellationToken = default); } } diff --git a/Avalonia-Services/Services/AuthService/AuthEndpointServices.cs b/Avalonia-Services/Services/AuthService/AuthEndpointServices.cs index 37bfa0b..8218001 100644 --- a/Avalonia-Services/Services/AuthService/AuthEndpointServices.cs +++ b/Avalonia-Services/Services/AuthService/AuthEndpointServices.cs @@ -3,21 +3,57 @@ using System.Threading.Tasks; namespace Avalonia_Services.Services.AuthService { + /// + /// API 鉴权端点服务接口,定义登录、刷新 Token 和登出操作。 + /// public interface IApiAuthEndpointService { + /// + /// 处理用户登录请求。 + /// + /// 服务端点上下文。 + /// 包含 Token 的认证响应。 Task LoginAsync(ServiceEndpointContext ctx); + /// + /// 使用 Refresh Token 刷新 Access Token。 + /// + /// 服务端点上下文。 + /// 新的 Token 对。 Task RefreshAsync(ServiceEndpointContext ctx); + /// + /// 处理用户登出请求。 + /// + /// 服务端点上下文。 + /// 登出结果。 Task LogoutAsync(ServiceEndpointContext ctx); } + /// + /// PC 端鉴权端点服务接口,定义授权码登录、Token 刷新和登出操作。 + /// public interface IPcAuthEndpointService { + /// + /// 使用授权码进行登录授权。 + /// + /// 服务端点上下文。 + /// 包含 Token 的认证响应。 Task AuthorizeAsync(ServiceEndpointContext ctx); + /// + /// 刷新当前 Token。 + /// + /// 服务端点上下文。 + /// 新的 Token 响应。 Task RefreshAsync(ServiceEndpointContext ctx); + /// + /// 处理用户登出请求。 + /// + /// 服务端点上下文。 + /// 登出结果。 Task LogoutAsync(ServiceEndpointContext ctx); } } diff --git a/Avalonia-Services/Services/WeatherForecastService.cs b/Avalonia-Services/Services/WeatherForecastService.cs index 94a1ece..4ed23af 100644 --- a/Avalonia-Services/Services/WeatherForecastService.cs +++ b/Avalonia-Services/Services/WeatherForecastService.cs @@ -2,6 +2,9 @@ using Avalonia_EFCore.Models; namespace Avalonia_Services.Services { + /// + /// 天气预报服务,随机生成未来 5 天的天气预报数据。 + /// public class WeatherForecastService { private static readonly string[] Summaries = @@ -9,6 +12,10 @@ namespace Avalonia_Services.Services "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" ]; + /// + /// 生成未来 5 天的随机天气预报数据。 + /// + /// 天气预报数据集合。 public IEnumerable GetWeatherForecasts() { return Enumerable.Range(1, 5).Select(index => new WeatherForecast diff --git a/scripts/find-missing-csharp-docs.ps1 b/scripts/find-missing-csharp-docs.ps1 index 1148ef2..de476e4 100644 --- a/scripts/find-missing-csharp-docs.ps1 +++ b/scripts/find-missing-csharp-docs.ps1 @@ -40,11 +40,11 @@ $memberRegexes = @( }, @{ Kind = "Property" - Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|virtual|abstract|sealed|override|new|readonly|required|unsafe)\s+)+[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s]*\s+[A-Za-z_][A-Za-z0-9_]*\s*\{\s*(?:get|set|init)\b' + Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|virtual|abstract|sealed|override|new|readonly|required|unsafe)\s+)+(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+[A-Za-z_][A-Za-z0-9_]*\s*\{\s*(?:get|set|init)\b' }, @{ Kind = "InterfaceProperty" - Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s]*\s+[A-Za-z_][A-Za-z0-9_]*\s*\{\s*(?:get|set|init)\b' + Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+[A-Za-z_][A-Za-z0-9_]*\s*\{\s*(?:get|set|init)\b' }, @{ Kind = "Constructor" @@ -52,15 +52,15 @@ $memberRegexes = @( }, @{ Kind = "Method" - Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|virtual|abstract|sealed|override|async|extern|new|unsafe|partial)\s+)+[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s]*\s+(?:operator\s*[^\s\(]+|[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(' + Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|virtual|abstract|sealed|override|async|extern|new|unsafe|partial)\s+)+(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+(?:operator\s*[^\s\(]+|[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(' }, @{ Kind = "InterfaceMethod" - Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s]*\s+[A-Za-z_][A-Za-z0-9_]*(?:<[^>]+>)?\s*\([^;{}]*\)\s*;' + Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+[A-Za-z_][A-Za-z0-9_]*(?:<[^>]+>)?\s*\([^;{}]*\)\s*;' }, @{ Kind = "Field" - Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|readonly|const|volatile|new|unsafe)\s+)+[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s]*\s+[A-Za-z_][A-Za-z0-9_]*(?:\s*=\s*[^;]+)?\s*;' + Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|readonly|const|volatile|new|unsafe)\s+)+(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+[A-Za-z_][A-Za-z0-9_]*(?:\s*=\s*[^;]+)?\s*;' } ) @@ -230,17 +230,15 @@ function Get-MemberName { } } "Method" { - $openParenIndex = $Declaration.IndexOf("(") - $prefix = if ($openParenIndex -ge 0) { $Declaration.Substring(0, $openParenIndex) } else { $Declaration } - if ($prefix -match '(?operator\s*[^\s]+|[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?$') { - return $Matches["name"] + $matches = [regex]::Matches($Declaration, '\s(?operator\s*[^\s\(]+|[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(') + if ($matches.Count -gt 0) { + return $matches[$matches.Count - 1].Groups["name"].Value } } "InterfaceMethod" { - $openParenIndex = $Declaration.IndexOf("(") - $prefix = if ($openParenIndex -ge 0) { $Declaration.Substring(0, $openParenIndex) } else { $Declaration } - if ($prefix -match '(?[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?$') { - return $Matches["name"] + $matches = [regex]::Matches($Declaration, '\s(?[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(') + if ($matches.Count -gt 0) { + return $matches[$matches.Count - 1].Groups["name"].Value } } default { diff --git a/scripts/missing-csharp-docs.after.json b/scripts/missing-csharp-docs.after.json new file mode 100644 index 0000000..6cba1b5 --- /dev/null +++ b/scripts/missing-csharp-docs.after.json @@ -0,0 +1,23 @@ +[ + { + "File": "Avalonia-API\\Authentication\\JwtTokenService.cs", + "Line": 20, + "Kind": "Method", + "Name": "CreateAccessToken", + "Declaration": "public (string Token, DateTime ExpiresAt) CreateAccessToken(UserEntity user, IReadOnlyCollection\u003cstring\u003e roles) {" + }, + { + "File": "Avalonia-API\\Authentication\\RefreshTokenService.cs", + "Line": 21, + "Kind": "Method", + "Name": "CreateAsync", + "Declaration": "public async Task\u003c(string Token, ApiRefreshTokenEntity Entity)\u003e CreateAsync( int userId, string? device, string? ipAddress, CancellationToken cancellationToken = default) {" + }, + { + "File": "Avalonia-API\\Authentication\\RefreshTokenService.cs", + "Line": 78, + "Kind": "Method", + "Name": "RotateAsync", + "Declaration": "public async Task\u003c(string Token, ApiRefreshTokenEntity Entity)?\u003e RotateAsync( string? token, string? device, string? ipAddress, CancellationToken cancellationToken = default) {" + } +] diff --git a/scripts/missing-csharp-docs.txt b/scripts/missing-csharp-docs.txt index 81d15f6..e02abfc 100644 --- a/scripts/missing-csharp-docs.txt +++ b/scripts/missing-csharp-docs.txt @@ -1,215 +1 @@  -File Line Kind Name Declaration ----- ---- ---- ---- ----------- -Avalonia-API\Authentication\ApiAuthEndpointService.cs 11 Type ApiAuthEndpointService public sealed class ApiAuthEndpointService( AppDataContext db, JwtTokenService jwtTokenService, RefreshTokenServ... -Avalonia-API\Authentication\ApiAuthEndpointService.cs 21 Method LoginAsync public async Task LoginAsync(ServiceEndpointContext ctx) { -Avalonia-API\Authentication\ApiAuthEndpointService.cs 59 Method RefreshAsync public async Task RefreshAsync(ServiceEndpointContext ctx) { -Avalonia-API\Authentication\ApiAuthEndpointService.cs 91 Method LogoutAsync public async Task LogoutAsync(ServiceEndpointContext ctx) { -Avalonia-API\Authentication\ApiAuthEndpointService.cs 98 Method Deserialize private static T? Deserialize(string? body) { -Avalonia-API\Authentication\ApiAuthEndpointService.cs 105 Method GetRemoteIpAddress private static string? GetRemoteIpAddress(ServiceEndpointContext ctx) { -Avalonia-API\Authentication\ApiAuthEndpointService.cs 112 Method NormalizeRoles private static string[] NormalizeRoles(string[]? roles) { -Avalonia-API\Authentication\JwtOptions.cs 3 Type JwtOptions public sealed class JwtOptions { -Avalonia-API\Authentication\JwtOptions.cs 5 Property Issuer public string Issuer { get; set; } = ""; -Avalonia-API\Authentication\JwtOptions.cs 7 Property Audience public string Audience { get; set; } = ""; -Avalonia-API\Authentication\JwtOptions.cs 9 Property SigningKey public string SigningKey { get; set; } = ""; -Avalonia-API\Authentication\JwtOptions.cs 11 Property AccessTokenMinutes public int AccessTokenMinutes { get; set; } = 60; -Avalonia-API\Authentication\JwtOptions.cs 13 Property RefreshTokenDays public int RefreshTokenDays { get; set; } = 30; -Avalonia-API\Authentication\JwtTokenService.cs 10 Type JwtTokenService public sealed class JwtTokenService(IOptions options) { -Avalonia-API\Authentication\JwtTokenService.cs 12 Field _options private readonly JwtOptions _options = options.Value; -Avalonia-API\Authentication\RefreshTokenService.cs 10 Type RefreshTokenService public sealed class RefreshTokenService(AppDataContext db, IOptions options) { -Avalonia-API\Authentication\RefreshTokenService.cs 12 Field _options private readonly JwtOptions _options = options.Value; -Avalonia-API\Authentication\RefreshTokenService.cs 35 Method FindActiveAsync public async Task FindActiveAsync(string? token, CancellationToken cancellationToken = d... -Avalonia-API\Authentication\RefreshTokenService.cs 47 Method RevokeAsync public async Task RevokeAsync(string? token, CancellationToken cancellationToken = default) { -Avalonia-API\Authentication\RefreshTokenService.cs 78 Method HashToken private static string HashToken(string token) { -Avalonia-API\Configuration\ServicesConfiguration.cs 13 Type ServicesConfiguration public static class ServicesConfiguration { -Avalonia-API\Extensions\UnifiedEndpointExtensions.cs 98 Method MapEndpoint private static RouteHandlerBuilder MapEndpoint( IEndpointRouteBuilder group, ServiceEndpoint endpoint, IServiceP... -Avalonia-API\Extensions\UnifiedEndpointExtensions.cs 115 Method CreateAspNetCoreHandler private static Delegate CreateAspNetCoreHandler( Func> unifiedHandler, ISe... -Avalonia-API\Extensions\UnifiedEndpointExtensions.cs 138 Method BuildContextFromHttpContext private static async Task BuildContextFromHttpContext(HttpContext httpContext) { -Avalonia-API\Extensions\UnifiedEndpointExtensions.cs 169 Method ConvertFilterAsync private static async ValueTask ConvertFilterAsync( UnifiedFilter unifiedFilter, AspNetCoreFilterContext... -Avalonia-Common\Core\ApiResponse.cs 119 Property Success public bool Success { get; set; } = true; -Avalonia-Common\Core\ApiResponse.cs 122 Property Code public int Code { get; set; } = 200; -Avalonia-Common\Core\ApiResponse.cs 125 Property Items public List Items { get; set; } = new(); -Avalonia-Common\Core\ApiResponse.cs 128 Property Total public int Total { get; set; } -Avalonia-Common\Core\ApiResponse.cs 131 Property Page public int Page { get; set; } = 1; -Avalonia-Common\Core\ApiResponse.cs 134 Property PageSize public int PageSize { get; set; } = 20; -Avalonia-Common\Core\ApiResponse.cs 137 Field TotalPages public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)Total / PageSize) : 0; -Avalonia-Common\Core\ApiResponse.cs 139 Method From public static PagedResponse From(List items, int total, int page, int pageSize) { -Avalonia-Common\Infrastructure\LoggingConfiguration.cs 90 Field _logger private static ILogger? _logger; -Avalonia-Common\Infrastructure\LoggingConfiguration.cs 101 Field Logger public static ILogger Logger => _logger ?? Log.Logger; -Avalonia-Common\Infrastructure\LoggingConfiguration.cs 103 Method Debug public static void Debug(string messageTemplate, params object?[] propertyValues) => Logger.Debug(messageTemplat... -Avalonia-Common\Infrastructure\LoggingConfiguration.cs 106 Method Information public static void Information(string messageTemplate, params object?[] propertyValues) => Logger.Information(me... -Avalonia-Common\Infrastructure\LoggingConfiguration.cs 109 Method Warning public static void Warning(string messageTemplate, params object?[] propertyValues) => Logger.Warning(messageTem... -Avalonia-Common\Infrastructure\LoggingConfiguration.cs 112 Method Error public static void Error(string messageTemplate, params object?[] propertyValues) => Logger.Error(messageTemplat... -Avalonia-Common\Infrastructure\LoggingConfiguration.cs 115 Method Error public static void Error(Exception exception, string messageTemplate, params object?[] propertyValues) => Logger... -Avalonia-Common\Infrastructure\LoggingConfiguration.cs 118 Method Fatal public static void Fatal(string messageTemplate, params object?[] propertyValues) => Logger.Fatal(messageTemplat... -Avalonia-Common\Infrastructure\LoggingConfiguration.cs 121 Method Fatal public static void Fatal(Exception exception, string messageTemplate, params object?[] propertyValues) => Logger... -Avalonia-EFCore\Database\AppDataContext.cs 22 Method OnModelCreating protected override void OnModelCreating(ModelBuilder modelBuilder) { -Avalonia-EFCore\Database\AppDataContextFactory.cs 5 Type AppDataContextFactory public class AppDataContextFactory : IDesignTimeDbContextFactory { -Avalonia-EFCore\Database\AppDataContextFactory.cs 7 Method CreateDbContext public AppDataContext CreateDbContext(string[] args) { -Avalonia-EFCore\Database\AppDbContext.cs 15 Field _dbConfig private readonly DatabaseConfiguration _dbConfig = dbConfig; -Avalonia-EFCore\Database\AppDbContext.cs 17 Method OnConfiguring protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { -Avalonia-EFCore\Database\AppDbContext.cs 68 Method SaveChangesAsync public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken =... -Avalonia-EFCore\Database\AppDbContext.cs 74 Method SetTimestamps private void SetTimestamps() { -Avalonia-EFCore\Database\DatabaseManager.cs 20 Field _context private readonly TContext _context; -Avalonia-EFCore\Database\DatabaseManager.cs 21 Field _config private readonly DatabaseConfiguration _config; -Avalonia-EFCore\Database\DatabaseManager.cs 22 Field _serviceProvider private readonly IServiceProvider? _serviceProvider; -Avalonia-EFCore\Database\DatabaseManager.cs 24 Constructor DatabaseManager public DatabaseManager(TContext context, DatabaseConfiguration config, IServiceProvider? serviceProvider = null) { -Avalonia-EFCore\Database\DatabaseManager.cs 131 Method GetApplicationVersion private static string GetApplicationVersion() { -Avalonia-EFCore\Database\DatabaseManager.cs 184 Property Provider public string Provider { get; set; } = string.Empty; -Avalonia-EFCore\Database\DatabaseManager.cs 185 Property AppliedMigrations public List AppliedMigrations { get; set; } = new(); -Avalonia-EFCore\Database\DatabaseManager.cs 186 Property PendingMigrations public List PendingMigrations { get; set; } = new(); -Avalonia-EFCore\Database\DatabaseManager.cs 187 Property IsLatest public bool IsLatest { get; set; } -Avalonia-EFCore\Database\DatabaseManager.cs 188 Property CanConnect public bool CanConnect { get; set; } -Avalonia-EFCore\Database\DatabaseProviderRegistry.cs 18 Field _providers private static readonly Dictionary _providers = new(); -Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 17 Property Id public long Id { get; set; } -Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 20 Property UserId public int UserId { get; set; } -Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 24 Property TokenHash public string TokenHash { get; set; } = string.Empty; -Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 27 Property CreatedAt public DateTime CreatedAt { get; set; } = DateTime.UtcNow; -Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 30 Property ExpiresAt public DateTime ExpiresAt { get; set; } -Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 33 Property RevokedAt public DateTime? RevokedAt { get; set; } -Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 37 Property ReplacedByTokenHash public string? ReplacedByTokenHash { get; set; } -Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 41 Property Device public string? Device { get; set; } -Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 45 Property IpAddress public string? IpAddress { get; set; } -Avalonia-EFCore\Models\ApiRefreshTokenEntity.cs 47 Field IsActive public bool IsActive => RevokedAt is null && ExpiresAt > DateTime.UtcNow; -Avalonia-EFCore\Models\UserEntity.cs 18 Property Id public int Id { get; set; } -Avalonia-EFCore\Models\UserEntity.cs 23 Property Name public string? Name { get; set; } -Avalonia-EFCore\Models\UserEntity.cs 28 Property Email public string? Email { get; set; } -Avalonia-EFCore\Models\UserEntity.cs 33 Property PhoneNumber public string? PhoneNumber { get; set; } -Avalonia-EFCore\Models\UserEntity.cs 37 Property CreatedAt public DateTime CreatedAt { get; set; } = DateTime.UtcNow; -Avalonia-EFCore\Models\UserEntity.cs 41 Property UpdatedAt public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; -Avalonia-EFCore\Models\WeatherForecast.cs 3 Type WeatherForecast public class WeatherForecast { -Avalonia-EFCore\Models\WeatherForecast.cs 5 Property Date public DateOnly Date { get; set; } -Avalonia-EFCore\Models\WeatherForecast.cs 7 Property TemperatureC public int TemperatureC { get; set; } -Avalonia-EFCore\Models\WeatherForecast.cs 9 Field TemperatureF public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); -Avalonia-EFCore\Models\WeatherForecast.cs 11 Property Summary public string? Summary { get; set; } -Avalonia-EFCore\Models\WeatherForecastEntity.cs 18 Property Id public int Id { get; set; } -Avalonia-EFCore\Models\WeatherForecastEntity.cs 22 Property Date public DateOnly Date { get; set; } -Avalonia-EFCore\Models\WeatherForecastEntity.cs 26 Property TemperatureC public int TemperatureC { get; set; } -Avalonia-EFCore\Models\WeatherForecastEntity.cs 31 Property Summary public string? Summary { get; set; } -Avalonia-EFCore\Models\WeatherForecastEntity.cs 35 Property CreatedAt public DateTime CreatedAt { get; set; } = DateTime.UtcNow; -Avalonia-EFCore\Models\WeatherForecastEntity.cs 39 Property UpdatedAt public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; -Avalonia-PC\App.axaml.cs 10 Type App public partial class App : Application { -Avalonia-PC\App.axaml.cs 12 Method Initialize public override void Initialize() { -Avalonia-PC\App.axaml.cs 17 Method OnFrameworkInitializationCompleted public override void OnFrameworkInitializationCompleted() { -Avalonia-PC\Authentication\DefaultPcThirdPartyAuthorizationClient.cs 13 Method ValidateAuthorizationCodeAsync public Task ValidateAuthorizationCodeAsync( string authorizationCode, CancellationTok... -Avalonia-PC\Authentication\DefaultPcThirdPartyAuthorizationClient.cs 26 Method RefreshAuthorizationAsync public Task RefreshAuthorizationAsync( string authorizationReference, CancellationTok... -Avalonia-PC\Authentication\PcAuthEndpointService.cs 11 Type PcAuthEndpointService public sealed class PcAuthEndpointService(PcGlobalTokenService tokenService) : IPcAuthEndpointService { -Avalonia-PC\Authentication\PcAuthEndpointService.cs 18 Method AuthorizeAsync public async Task AuthorizeAsync(ServiceEndpointContext ctx) { -Avalonia-PC\Authentication\PcAuthEndpointService.cs 31 Method RefreshAsync public async Task RefreshAsync(ServiceEndpointContext ctx) { -Avalonia-PC\Authentication\PcAuthEndpointService.cs 45 Method LogoutAsync public Task LogoutAsync(ServiceEndpointContext ctx) { -Avalonia-PC\Authentication\PcAuthEndpointService.cs 53 Method Deserialize private static T? Deserialize(string? body) { -Avalonia-PC\Authentication\PcAuthEndpointService.cs 60 Method ExtractBearerToken private static string? ExtractBearerToken(string? authorization) { -Avalonia-PC\Authentication\PcAuthEndpointService.cs 67 Field prefix const string prefix = ""; -Avalonia-PC\Authentication\PcAuthService.cs 9 Type PcAuthService public sealed class PcAuthService(PcGlobalTokenService tokenService) : IAuthService { -Avalonia-PC\Authentication\PcAuthService.cs 11 Method AuthenticateAsync public async Task AuthenticateAsync(ServiceEndpointContext context) { -Avalonia-PC\Authentication\PcAuthService.cs 32 Method AuthorizeAsync public Task AuthorizeAsync(ClaimsPrincipal user, string policy) { -Avalonia-PC\Authentication\PcAuthService.cs 37 Method ExtractBearerToken private static string? ExtractBearerToken(string? authorization) { -Avalonia-PC\Authentication\PcAuthService.cs 44 Field prefix const string prefix = ""; -Avalonia-PC\Authentication\PcGlobalTokenService.cs 10 Type PcGlobalTokenService public sealed class PcGlobalTokenService(IPcThirdPartyAuthorizationClient thirdPartyClient) { -Avalonia-PC\Authentication\PcGlobalTokenService.cs 12 Field SuperRoles private static readonly string[] SuperRoles = ["", ""]; -Avalonia-PC\Authentication\PcGlobalTokenService.cs 13 Field _syncRoot private readonly object _syncRoot = new(); -Avalonia-PC\Authentication\PcGlobalTokenService.cs 14 Field _current private PcTokenState? _current; -Avalonia-PC\Authentication\PcGlobalTokenService.cs 16 Field NormalLifetime private static readonly TimeSpan NormalLifetime = TimeSpan.FromHours(8); -Avalonia-PC\Authentication\PcGlobalTokenService.cs 17 Field TemporaryFailureLifetime private static readonly TimeSpan TemporaryFailureLifetime = TimeSpan.FromMinutes(20); -Avalonia-PC\Authentication\PcGlobalTokenService.cs 18 Field MaxTemporaryFailureWindow private static readonly TimeSpan MaxTemporaryFailureWindow = TimeSpan.FromHours(24); -Avalonia-PC\Authentication\PcGlobalTokenService.cs 20 Method AuthorizeAsync public async Task AuthorizeAsync(string? authorizationCode, CancellationToken cancellationToke... -Avalonia-PC\Authentication\PcGlobalTokenService.cs 36 Method RefreshAsync public async Task RefreshAsync(string? token, CancellationToken cancellationToken = default) { -Avalonia-PC\Authentication\PcGlobalTokenService.cs 59 Method ValidateAsync public async Task ValidateAsync(string? token, CancellationToken cancellationToken = default) { -Avalonia-PC\Authentication\PcGlobalTokenService.cs 79 Method Logout public void Logout(string? token) { -Avalonia-PC\Authentication\PcGlobalTokenService.cs 90 Method IssueToken private PcTokenResponse IssueToken(string authorizationReference, TimeSpan lifetime, bool resetTemporaryFailureW... -Avalonia-PC\Authentication\PcGlobalTokenService.cs 108 Method RefreshAfterTemporaryFailure private PcTokenResponse? RefreshAfterTemporaryFailure(PcTokenState current) { -Avalonia-PC\Authentication\PcGlobalTokenService.cs 119 Method ClearAndReturnNull private PcTokenResponse? ClearAndReturnNull() { -Avalonia-PC\Authentication\PcGlobalTokenService.cs 129 Method IsCurrentToken private bool IsCurrentToken(string? token) { -Avalonia-PC\Authentication\PcGlobalTokenService.cs 136 Method HashToken private static string HashToken(string token) { -Avalonia-PC\Authentication\PcGlobalTokenService.cs 142 Type PcTokenState private sealed record PcTokenState( string TokenHash, string AuthorizationReference, DateTime ExpiresAt, DateTim... -Avalonia-PC\Program.cs 17 Type Program internal sealed class Program { -Avalonia-PC\Program.cs 19 Property Services public static IServiceProvider Services { get; private set; } = null!; -Avalonia-PC\Program.cs 22 Method Main public static void Main(string[] args) { -Avalonia-PC\Program.cs 43 Method ConfigureServices private static void ConfigureServices() { -Avalonia-PC\Program.cs 75 Method BuildAvaloniaApp public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() -Avalonia-PC\ViewLocator.cs 15 Type ViewLocator public class ViewLocator : IDataTemplate { -Avalonia-PC\ViewLocator.cs 17 Method Build public Control? Build(object? param) { -Avalonia-PC\ViewLocator.cs 33 Method Match public bool Match(object? data) { -Avalonia-PC\ViewModels\MainWindowViewModel.cs 3 Type MainWindowViewModel public partial class MainWindowViewModel : ViewModelBase { -Avalonia-PC\ViewModels\MainWindowViewModel.cs 5 Property Greeting public string Greeting { get; } = ""; -Avalonia-PC\ViewModels\ViewModelBase.cs 5 Type ViewModelBase public abstract class ViewModelBase : ObservableObject { -Avalonia-PC\Views\MainWindow.axaml.cs 15 Type MainWindow public partial class MainWindow : Window { -Avalonia-PC\Views\MainWindow.axaml.cs 17 Field AppScheme private const string AppScheme = ""; -Avalonia-PC\Views\MainWindow.axaml.cs 18 Field OnlineStartupUrl private const string? OnlineStartupUrl = ""; -Avalonia-PC\Views\MainWindow.axaml.cs 20 Field LocalStartupPath private const string? LocalStartupPath = null; -Avalonia-PC\Views\MainWindow.axaml.cs 26 Field _webView private NativeWebView? _webView; -Avalonia-PC\Views\MainWindow.axaml.cs 27 Field _eventsAttached private bool _eventsAttached; -Avalonia-PC\Views\MainWindow.axaml.cs 28 Field _webViewAdapter private object? _webViewAdapter; -Avalonia-PC\Views\MainWindow.axaml.cs 29 Field _localHttpServer private HttpListener? _localHttpServer; -Avalonia-PC\Views\MainWindow.axaml.cs 30 Field _localHttpServerCts private CancellationTokenSource? _localHttpServerCts; -Avalonia-PC\Views\MainWindow.axaml.cs 31 Field _localHttpBaseUrl private string? _localHttpBaseUrl; -Avalonia-PC\Views\MainWindow.axaml.cs 32 Field _localHttpRoot private string? _localHttpRoot; -Avalonia-PC\Views\MainWindow.axaml.cs 613 Type AppResponse private sealed class AppResponse { -Avalonia-PC\Views\MainWindow.axaml.cs 615 Property Kind public string Kind { get; set; } = string.Empty; -Avalonia-PC\Views\MainWindow.axaml.cs 617 Property Id public string? Id { get; set; } -Avalonia-PC\Views\MainWindow.axaml.cs 619 Property StatusCode public int StatusCode { get; set; } -Avalonia-PC\Views\MainWindow.axaml.cs 621 Property StatusMessage public string StatusMessage { get; set; } = string.Empty; -Avalonia-PC\Views\MainWindow.axaml.cs 623 Property Body public string Body { get; set; } = string.Empty; -Avalonia-PC\Views\MainWindow.axaml.cs 625 Property Headers public Dictionary Headers { get; set; } = new(); -Avalonia-PC\Views\MainWindow.BridgeScript.cs 3 Type MainWindow public partial class MainWindow { -Avalonia-PC\Views\MainWindow.BridgeScript.cs 109 Type BridgeXMLHttpRequest class BridgeXMLHttpRequest { -Avalonia-PC\Views\MainWindow.Routes.cs 12 Type MainWindow public partial class MainWindow { -Avalonia-PC\Views\MainWindow.Routes.cs 25 Method RegisterRoutes private void RegisterRoutes() { -Avalonia-Services\Core\GlobalExceptionFilter.cs 13 Field _includeDetails private readonly bool _includeDetails; -Avalonia-Services\Core\GlobalExceptionFilter.cs 23 Method InvokeAsync public async Task InvokeAsync(ServiceEndpointContext context, EndpointFilterDelegate next) { -Avalonia-Services\Core\GlobalExceptionFilter.cs 76 Method LogException private static void LogException(ServiceEndpointContext context, Exception ex) { -Avalonia-Services\Core\IAuthService.cs 26 Method AuthenticateAsync public Task AuthenticateAsync(ServiceEndpointContext context) { -Avalonia-Services\Core\IAuthService.cs 33 Method AuthorizeAsync public Task AuthorizeAsync(ClaimsPrincipal user, string policy) { -Avalonia-Services\Core\IEndpointFilter.cs 28 Field _filter private readonly Func _filter; -Avalonia-Services\Core\IEndpointFilter.cs 30 Constructor AnonymousEndpointFilter public AnonymousEndpointFilter(Func filter) { -Avalonia-Services\Core\IEndpointFilter.cs 35 Method InvokeAsync public Task InvokeAsync(ServiceEndpointContext context, EndpointFilterDelegate next) { -Avalonia-Services\Core\ServiceEndpointCollection.cs 9 Type EndpointHostTarget public enum EndpointHostTarget { -Avalonia-Services\Core\ServiceEndpointCollection.cs 72 Method WithOpenApi public ServiceEndpoint WithOpenApi( string tag, string summary, string? description = null, Type? requestType = ... -Avalonia-Services\Core\ServiceEndpointCollection.cs 125 Method SupportsHost public bool SupportsHost(EndpointHostTarget host) { -Avalonia-Services\Core\ServiceEndpointCollection.cs 139 Method ForHost public IEnumerable ForHost(EndpointHostTarget host) { -Avalonia-Services\Core\ServiceEndpointCollection.cs 155 Method MapGet public ServiceEndpoint MapGet( string pattern, Func> h... -Avalonia-Services\Core\ServiceEndpointCollection.cs 171 Method MapPost public ServiceEndpoint MapPost( string pattern, Func> ... -Avalonia-Services\Core\ServiceEndpointCollection.cs 187 Method MapPut public ServiceEndpoint MapPut( string pattern, Func> h... -Avalonia-Services\Core\ServiceEndpointCollection.cs 203 Method MapDelete public ServiceEndpoint MapDelete( string pattern, Func... -Avalonia-Services\Core\ServiceEndpointCollection.cs 229 Method AddEndpoint private ServiceEndpoint AddEndpoint(string pattern, string method, Func> h... -Avalonia-Services\Core\ServiceEndpointCollection.cs 241 Method CreateServiceHandler private static Func> CreateServiceHandler( Func GetUserFromDatabaseAsync(ServiceEndpointContext ctx) { -Avalonia-Services\Endpoints\AppEndpoints.cs 112 Method ProcessDataAsync private static async Task ProcessDataAsync(ServiceEndpointContext ctx) { -Avalonia-Services\Endpoints\AuthEndpoints.cs 11 Method ConfigureApi public static void ConfigureApi(ServiceEndpointBuilder builder) { -Avalonia-Services\Endpoints\AuthEndpoints.cs 32 Method ConfigurePc public static void ConfigurePc(ServiceEndpointBuilder builder) { -Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 11 Field _endpoints private readonly ServiceEndpointCollection _endpoints; -Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 12 Field _authService private readonly IAuthService _authService; -Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 13 Field _serviceProvider private readonly IServiceProvider _serviceProvider; -Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 20 Property IsMatched public bool IsMatched { get; init; } -Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 21 Property StatusCode public int StatusCode { get; init; } = 200; -Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 22 Property StatusMessage public string StatusMessage { get; init; } = ""; -Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 23 Property Data public object? Data { get; init; } -Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 24 Property ResponseHeaders public Dictionary ResponseHeaders { get; init; } = new(); -Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 26 Method Success public static RouteResult Success(object? data, ServiceEndpointContext ctx) { -Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 38 Method NotFound public static RouteResult NotFound() => new() -Avalonia-Services\Extensions\DesktopEndpointAdapter.cs 46 Constructor DesktopEndpointAdapter public DesktopEndpointAdapter( ServiceEndpointCollection endpoints, IAuthService authService, IServiceProvider s... -Avalonia-Services\Services\AuthService\AuthContracts.cs 3 Type ApiLoginRequest public sealed record ApiLoginRequest(string? Account, string? Password, string[]? Roles = null); -Avalonia-Services\Services\AuthService\AuthContracts.cs 5 Type ApiRefreshTokenRequest public sealed record ApiRefreshTokenRequest(string? RefreshToken); -Avalonia-Services\Services\AuthService\AuthContracts.cs 7 Type ApiLogoutRequest public sealed record ApiLogoutRequest(string? RefreshToken); -Avalonia-Services\Services\AuthService\AuthContracts.cs 9 Type AuthTokenResponse public sealed record AuthTokenResponse( string AccessToken, string RefreshToken, DateTime AccessTokenExpiresAt, ... -Avalonia-Services\Services\AuthService\AuthContracts.cs 16 Type PcAuthorizeRequest public sealed record PcAuthorizeRequest(string? AuthorizationCode); -Avalonia-Services\Services\AuthService\AuthContracts.cs 18 Type PcRefreshRequest public sealed record PcRefreshRequest(string? Token); -Avalonia-Services\Services\AuthService\AuthContracts.cs 20 Type PcLogoutRequest public sealed record PcLogoutRequest(string? Token); -Avalonia-Services\Services\AuthService\AuthContracts.cs 22 Type PcTokenResponse public sealed record PcTokenResponse(string Token, DateTime ExpiresAt, string[] Roles); -Avalonia-Services\Services\AuthService\AuthContracts.cs 24 Type ThirdPartyAuthCheckResult public enum ThirdPartyAuthCheckResult { -Avalonia-Services\Services\AuthService\AuthContracts.cs 31 Type IPcThirdPartyAuthorizationClient public interface IPcThirdPartyAuthorizationClient { -Avalonia-Services\Services\AuthService\AuthContracts.cs 33 InterfaceMethod ValidateAuthorizationCodeAsync Task ValidateAuthorizationCodeAsync(string authorizationCode, CancellationToken cance... -Avalonia-Services\Services\AuthService\AuthContracts.cs 35 InterfaceMethod RefreshAuthorizationAsync Task RefreshAuthorizationAsync(string authorizationReference, CancellationToken cance... -Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 6 Type IApiAuthEndpointService public interface IApiAuthEndpointService { -Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 8 InterfaceMethod LoginAsync Task LoginAsync(ServiceEndpointContext ctx); -Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 10 InterfaceMethod RefreshAsync Task RefreshAsync(ServiceEndpointContext ctx); -Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 12 InterfaceMethod LogoutAsync Task LogoutAsync(ServiceEndpointContext ctx); -Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 15 Type IPcAuthEndpointService public interface IPcAuthEndpointService { -Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 17 InterfaceMethod AuthorizeAsync Task AuthorizeAsync(ServiceEndpointContext ctx); -Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 19 InterfaceMethod RefreshAsync Task RefreshAsync(ServiceEndpointContext ctx); -Avalonia-Services\Services\AuthService\AuthEndpointServices.cs 21 InterfaceMethod LogoutAsync Task LogoutAsync(ServiceEndpointContext ctx); -Avalonia-Services\Services\WeatherForecastService.cs 5 Type WeatherForecastService public class WeatherForecastService { -Avalonia-Services\Services\WeatherForecastService.cs 12 Method GetWeatherForecasts public IEnumerable GetWeatherForecasts() { - - -