docs: 补全全部缺失的 XML 文档注释(中文)

- 为全部 5 个项目(Avalonia-API、Avalonia-Common、Avalonia-EFCore、
  Avalonia-PC、Avalonia-Services)中缺失注释的类、方法、属性、字段、
  接口成员等补全中文 XML 文档注释
- 共修改约 37 个文件,补全约 220+ 处注释
- 修复 ServiceEndpointCollection.cs 中 MapDelete<TService> 语法错误
- 修复 PcAuthService.cs 中 const prefix 位置错乱导致编译失败的问题
- 扫描结果:缺失项 0
- 构建结果:4/4 项目编译通过
This commit is contained in:
2026-05-18 11:35:13 +08:00
parent 446502b0e9
commit fc6f9f6bc3
43 changed files with 884 additions and 233 deletions
+9
View File
@@ -7,13 +7,22 @@ using Microsoft.Extensions.DependencyInjection;
namespace Avalonia_PC
{
/// <summary>
/// Avalonia 应用程序入口类,负责初始化 XAML 资源和设置主窗口。
/// </summary>
public partial class App : Application
{
/// <summary>
/// 加载 Avalonia XAML 资源。
/// </summary>
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
/// <summary>
/// 框架初始化完成后设置主窗口和数据上下文。
/// </summary>
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
@@ -10,6 +10,12 @@ namespace Avalonia_PC.Authentication
/// </summary>
public sealed class DefaultPcThirdPartyAuthorizationClient : IPcThirdPartyAuthorizationClient
{
/// <summary>
/// 验证第三方授权码是否有效。默认实现将 "invalid" 视为授权丢失,其余视为有效。
/// </summary>
/// <param name="authorizationCode">第三方授权码。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>授权检查结果。</returns>
public Task<ThirdPartyAuthCheckResult> ValidateAuthorizationCodeAsync(
string authorizationCode,
CancellationToken cancellationToken = default)
@@ -23,6 +29,12 @@ namespace Avalonia_PC.Authentication
return Task.FromResult(ThirdPartyAuthCheckResult.Valid);
}
/// <summary>
/// 刷新第三方授权。默认实现总是返回 TemporaryFailure,表示暂时无法刷新。
/// </summary>
/// <param name="authorizationReference">授权引用标识。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>授权检查结果。</returns>
public Task<ThirdPartyAuthCheckResult> RefreshAuthorizationAsync(
string authorizationReference,
CancellationToken cancellationToken = default)
@@ -8,6 +8,10 @@ using System.Threading.Tasks;
namespace Avalonia_PC.Authentication
{
/// <summary>
/// PC 端鉴权端点服务,实现 <see cref="IPcAuthEndpointService"/>
/// 处理授权码登录、Token 刷新和登出操作。
/// </summary>
public sealed class PcAuthEndpointService(PcGlobalTokenService tokenService) : IPcAuthEndpointService
{
private static readonly JsonSerializerOptions JsonOptions = new()
@@ -15,6 +19,7 @@ namespace Avalonia_PC.Authentication
PropertyNameCaseInsensitive = true,
};
/// <inheritdoc />
public async Task<object?> AuthorizeAsync(ServiceEndpointContext ctx)
{
var request = Deserialize<PcAuthorizeRequest>(ctx.Body);
@@ -28,6 +33,7 @@ namespace Avalonia_PC.Authentication
return ResponseHelper.Ok(token, "授权成功");
}
/// <inheritdoc />
public async Task<object?> RefreshAsync(ServiceEndpointContext ctx)
{
var request = Deserialize<PcRefreshRequest>(ctx.Body);
@@ -42,6 +48,7 @@ namespace Avalonia_PC.Authentication
return ResponseHelper.Ok(refreshed, "刷新成功");
}
/// <inheritdoc />
public Task<object?> LogoutAsync(ServiceEndpointContext ctx)
{
var request = Deserialize<PcLogoutRequest>(ctx.Body);
@@ -50,6 +57,12 @@ namespace Avalonia_PC.Authentication
return Task.FromResult<object?>(ResponseHelper.Succeed("退出成功"));
}
/// <summary>
/// 将 JSON 请求体反序列化为指定类型。
/// </summary>
/// <typeparam name="T">目标类型。</typeparam>
/// <param name="body">JSON 请求体字符串,可为空。</param>
/// <returns>反序列化后的对象;若 body 为空则返回默认值。</returns>
private static T? Deserialize<T>(string? body)
{
return string.IsNullOrWhiteSpace(body)
@@ -57,6 +70,11 @@ namespace Avalonia_PC.Authentication
: JsonSerializer.Deserialize<T>(body, JsonOptions);
}
/// <summary>
/// 从 Authorization 头中提取 Bearer Token。
/// </summary>
/// <param name="authorization">Authorization 头的值。</param>
/// <returns>提取的 Token 字符串;若无法提取则返回 null。</returns>
private static string? ExtractBearerToken(string? authorization)
{
if (string.IsNullOrWhiteSpace(authorization))
@@ -64,7 +82,10 @@ namespace Avalonia_PC.Authentication
return null;
}
const string prefix = "Bearer ";
/// <summary>
/// Bearer Token 的前缀常量。
/// </summary>
const string prefix = "Bearer ";
return authorization.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
? authorization[prefix.Length..].Trim()
: authorization.Trim();
+11 -1
View File
@@ -6,8 +6,12 @@ using System.Threading.Tasks;
namespace Avalonia_PC.Authentication
{
/// <summary>
/// PC 端鉴权服务,基于全局 Token 验证用户身份,实现 <see cref="IAuthService"/>。
/// </summary>
public sealed class PcAuthService(PcGlobalTokenService tokenService) : IAuthService
{
/// <inheritdoc />
public async Task<ClaimsPrincipal?> AuthenticateAsync(ServiceEndpointContext context)
{
var token = ExtractBearerToken(context.GetHeader("Authorization"));
@@ -29,11 +33,17 @@ namespace Avalonia_PC.Authentication
return new ClaimsPrincipal(identity);
}
/// <inheritdoc />
public Task<bool> AuthorizeAsync(ClaimsPrincipal user, string policy)
{
return Task.FromResult(user.Identity?.IsAuthenticated == true);
}
/// <summary>
/// 从 Authorization 头中提取 Bearer Token。
/// </summary>
/// <param name="authorization">Authorization 头的值。</param>
/// <returns>提取的 Token 字符串;若无法提取则返回 null。</returns>
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();
@@ -7,16 +7,45 @@ using System.Threading.Tasks;
namespace Authentication
{
/// <summary>
/// PC 端全局 Token 服务,管理全局唯一的访问 Token。
/// 支持授权码登录、Token 刷新、有效性验证和登出,
/// 在第三方授权暂时失败时使用缩短有效期的临时 Token。
/// </summary>
public sealed class PcGlobalTokenService(IPcThirdPartyAuthorizationClient thirdPartyClient)
{
/// <summary>
/// 超级管理员角色集合。
/// </summary>
private static readonly string[] SuperRoles = ["SuperAdmin", "Admin"];
/// <summary>
/// 线程同步锁。
/// </summary>
private readonly object _syncRoot = new();
/// <summary>
/// 当前 Token 状态。
/// </summary>
private PcTokenState? _current;
/// <summary>
/// 正常 Token 有效期(8 小时)。
/// </summary>
private static readonly TimeSpan NormalLifetime = TimeSpan.FromHours(8);
/// <summary>
/// 第三方暂时失败时的 Token 有效期(20 分钟)。
/// </summary>
private static readonly TimeSpan TemporaryFailureLifetime = TimeSpan.FromMinutes(20);
/// <summary>
/// 第三方暂时失败的最长容忍窗口(24 小时),超出后清除 Token。
/// </summary>
private static readonly TimeSpan MaxTemporaryFailureWindow = TimeSpan.FromHours(24);
/// <summary>
/// 使用授权码进行登录授权,验证成功后颁发全局 Token。
/// </summary>
/// <param name="authorizationCode">第三方授权码。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>Token 响应;若授权码无效则返回 null。</returns>
public async Task<PcTokenResponse?> AuthorizeAsync(string? authorizationCode, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(authorizationCode))
@@ -33,6 +62,13 @@ namespace Authentication
return IssueToken(authorizationCode, NormalLifetime, resetTemporaryFailureWindow: true);
}
/// <summary>
/// 刷新当前 Token,向第三方验证授权引用是否仍然有效。
/// 根据第三方返回结果决定是续期、降级为临时 Token 还是清除。
/// </summary>
/// <param name="token">当前 Token。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>新的 Token 响应;若授权丢失则返回 null。</returns>
public async Task<PcTokenResponse?> RefreshAsync(string? token, CancellationToken cancellationToken = default)
{
PcTokenState? current;
@@ -56,6 +92,12 @@ namespace Authentication
};
}
/// <summary>
/// 验证 Token 是否有效,若已过期则尝试自动刷新。
/// </summary>
/// <param name="token">要验证的 Token。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>Token 是否有效。</returns>
public async Task<bool> ValidateAsync(string? token, CancellationToken cancellationToken = default)
{
PcTokenState? current;
@@ -76,6 +118,10 @@ namespace Authentication
return await RefreshAsync(token, cancellationToken) is not null;
}
/// <summary>
/// 登出并清除当前 Token。
/// </summary>
/// <param name="token">要清除的 Token。</param>
public void Logout(string? token)
{
lock (_syncRoot)
@@ -87,6 +133,13 @@ namespace Authentication
}
}
/// <summary>
/// 颁发新的全局 Token。
/// </summary>
/// <param name="authorizationReference">授权引用标识。</param>
/// <param name="lifetime">Token 有效期。</param>
/// <param name="resetTemporaryFailureWindow">是否重置暂时失败窗口。</param>
/// <returns>包含 Token 和过期时间的响应。</returns>
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);
}
/// <summary>
/// 在第三方暂时失败时刷新 Token。若超出最大容忍窗口则清除 Token。
/// </summary>
/// <param name="current">当前 Token 状态。</param>
/// <returns>新的临时 Token 响应;若超出容忍窗口则返回 null。</returns>
private PcTokenResponse? RefreshAfterTemporaryFailure(PcTokenState current)
{
var startedAt = current.TemporaryFailureStartedAt ?? DateTime.UtcNow;
@@ -116,6 +174,10 @@ namespace Authentication
return IssueToken(current.AuthorizationReference, TemporaryFailureLifetime, resetTemporaryFailureWindow: false);
}
/// <summary>
/// 清除当前 Token 并返回 null。
/// </summary>
/// <returns>始终返回 null。</returns>
private PcTokenResponse? ClearAndReturnNull()
{
lock (_syncRoot)
@@ -126,6 +188,11 @@ namespace Authentication
return null;
}
/// <summary>
/// 检查给定 Token 是否与当前持有的 Token 匹配。
/// </summary>
/// <param name="token">要检查的 Token。</param>
/// <returns>是否匹配。</returns>
private bool IsCurrentToken(string? token)
{
return !string.IsNullOrWhiteSpace(token) &&
@@ -133,12 +200,24 @@ namespace Authentication
string.Equals(_current.TokenHash, HashToken(token), StringComparison.Ordinal);
}
/// <summary>
/// 对 Token 原文进行 SHA256 哈希,返回十六进制字符串。
/// </summary>
/// <param name="token">Token 原文。</param>
/// <returns>SHA256 哈希后的十六进制字符串。</returns>
private static string HashToken(string token)
{
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(token));
return Convert.ToHexString(bytes);
}
/// <summary>
/// 保存当前全局 Token 的内部状态。
/// </summary>
/// <param name="TokenHash">Token 的 SHA256 哈希值。</param>
/// <param name="AuthorizationReference">授权引用标识。</param>
/// <param name="ExpiresAt">过期时间。</param>
/// <param name="TemporaryFailureStartedAt">第三方暂时失败的起始时间。</param>
private sealed record PcTokenState(
string TokenHash,
string AuthorizationReference,
+17 -1
View File
@@ -14,10 +14,20 @@ using System;
namespace Avalonia_PC
{
/// <summary>
/// 桌面应用程序入口类,负责配置 DI 容器、初始化数据库和启动 Avalonia 框架。
/// </summary>
internal sealed class Program
{
/// <summary>
/// 获取全局 DI 服务提供程序。
/// </summary>
public static IServiceProvider Services { get; private set; } = null!;
/// <summary>
/// 应用程序主入口点。
/// </summary>
/// <param name="args">命令行参数。</param>
[STAThread]
public static void Main(string[] args)
{
@@ -40,6 +50,9 @@ namespace Avalonia_PC
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
/// <summary>
/// 配置 DI 容器,注册数据库、业务服务、鉴权服务和统一端点。
/// </summary>
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.
/// <summary>
/// 构建 Avalonia 应用程序(供可视化设计器使用,请勿删除)。
/// </summary>
/// <returns>Avalonia 应用构建器。</returns>
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
+15
View File
@@ -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")]
/// <summary>
/// 视图定位器,根据 ViewModel 类型自动查找对应的 View,
/// 实现 IDataTemplate 以支持 Avalonia 的数据模板机制。
/// </summary>
public class ViewLocator : IDataTemplate
{
/// <summary>
/// 根据 ViewModel 实例构建对应的 View 控件。
/// 约定:将 ViewModels 命名空间中的 ViewModel 替换为 Views 命名空间中的同名 View。
/// </summary>
/// <param name="param">ViewModel 实例。</param>
/// <returns>对应的 View 控件;若未找到则返回 TextBlock 显示错误信息。</returns>
public Control? Build(object? param)
{
if (param is null)
@@ -30,6 +40,11 @@ namespace Avalonia_PC
return new TextBlock { Text = "Not Found: " + name };
}
/// <summary>
/// 判断数据对象是否为 ViewModel 类型(以 "ViewModel" 结尾)。
/// </summary>
/// <param name="data">要判断的数据对象。</param>
/// <returns>是否为 ViewModel。</returns>
public bool Match(object? data)
{
return data is ViewModelBase;
@@ -1,7 +1,13 @@
namespace Avalonia_PC.ViewModels
{
/// <summary>
/// 主窗口的 ViewModel,提供问候语等绑定属性。
/// </summary>
public partial class MainWindowViewModel : ViewModelBase
{
/// <summary>
/// 获取问候语文本。
/// </summary>
public string Greeting { get; } = "Welcome to Avalonia!";
}
}
+4
View File
@@ -2,6 +2,10 @@
namespace Avalonia_PC.ViewModels
{
/// <summary>
/// ViewModel 基类,继承自 CommunityToolkit.Mvvm 的 ObservableObject
/// 提供属性变更通知功能。
/// </summary>
public abstract class ViewModelBase : ObservableObject
{
}
@@ -1,5 +1,8 @@
namespace Avalonia_PC.Views
{
/// <summary>
/// MainWindow 的分部类,定义注入 WebView2 的 JavaScript Bridge 脚本。
/// </summary>
public partial class MainWindow
{
private const string BridgeScript = """
@@ -106,6 +109,9 @@ if (!window.__appBridgeInstalled) {
});
};
/// <summary>
/// WebView2 Bridge 中的 XMLHttpRequest 替代实现,将 app:// 请求拦截并转为 C# 调用。
/// </summary>
class BridgeXMLHttpRequest {
constructor() {
this._native = new NativeXMLHttpRequest();
+6
View File
@@ -9,6 +9,9 @@ using System.Threading.Tasks;
namespace Avalonia_PC.Views
{
/// <summary>
/// MainWindow 的分部类,负责路由注册和统一端点适配。
/// </summary>
public partial class MainWindow
{
/// <summary>
@@ -22,6 +25,9 @@ namespace Avalonia_PC.Views
/// </summary>
private IServiceProvider _services = null!;
/// <summary>
/// 从 DI 获取统一端点集合并构建桌面适配器。
/// </summary>
private void RegisterRoutes()
{
// 从 DI 获取已构建的端点集合
+54
View File
@@ -12,23 +12,56 @@ using System.Threading.Tasks;
namespace Avalonia_PC.Views
{
/// <summary>
/// 主窗口,承载 WebView2 控件并管理前后端 Bridge 通信。
/// </summary>
public partial class MainWindow : Window
{
/// <summary>
/// 自定义协议方案名称。
/// </summary>
private const string AppScheme = "app";
/// <summary>
/// 在线模式下的前端启动 URL。
/// </summary>
private const string? OnlineStartupUrl = "http://localhost:51240";
//private const string? OnlineStartupUrl = null;
/// <summary>
/// 离线模式下的前端本地文件路径,为空则使用在线模式。
/// </summary>
private const string? LocalStartupPath = null;
private static readonly JsonSerializerOptions BridgeJsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
/// <summary>
/// WebView2 原生控件实例。
/// </summary>
private NativeWebView? _webView;
/// <summary>
/// 标记 WebView 事件是否已绑定。
/// </summary>
private bool _eventsAttached;
/// <summary>
/// WebView 适配器对象。
/// </summary>
private object? _webViewAdapter;
/// <summary>
/// 本地 HTTP 服务器实例(离线模式)。
/// </summary>
private HttpListener? _localHttpServer;
/// <summary>
/// 本地 HTTP 服务器的取消令牌源。
/// </summary>
private CancellationTokenSource? _localHttpServerCts;
/// <summary>
/// 本地 HTTP 服务器的基础 URL。
/// </summary>
private string? _localHttpBaseUrl;
/// <summary>
/// 本地 HTTP 服务器的根目录路径。
/// </summary>
private string? _localHttpRoot;
#region WebView
@@ -610,18 +643,39 @@ namespace Avalonia_PC.Views
#region DTO /
/// <summary>
/// Bridge 通信响应 DTO,用于序列化返回给前端的数据。
/// </summary>
private sealed class AppResponse
{
/// <summary>
/// 获取或设置响应类型标识。
/// </summary>
public string Kind { get; set; } = string.Empty;
/// <summary>
/// 获取或设置请求 ID(对应前端请求)。
/// </summary>
public string? Id { get; set; }
/// <summary>
/// 获取或设置 HTTP 状态码。
/// </summary>
public int StatusCode { get; set; }
/// <summary>
/// 获取或设置状态描述文本。
/// </summary>
public string StatusMessage { get; set; } = string.Empty;
/// <summary>
/// 获取或设置响应体 JSON 字符串。
/// </summary>
public string Body { get; set; } = string.Empty;
/// <summary>
/// 获取或设置响应头字典。
/// </summary>
public Dictionary<string, string> Headers { get; set; } = new();
}