WebDAV 文件存储(Aegis.FileManager.WebDav)
Aegis.FileManager.WebDav 是 Aegis.FileManager 的 WebDAV 协议存储实现,对接 nginx 等 WebDAV 静态资源站。零第三方依赖:纯 HttpClient 发请求,System.Xml.Linq 解析 PROPFIND 响应,不引任何 SDK。
StorageKey(ObjectKey)的拼装规则属于业务领域,详见 文件管理 — 核心概念:业务自拼 Key。本页只讲 WebDAV 后端的接入与配置。
组件概览
| 字段 | 说明 |
|---|---|
| 组件名称 | WebDAV 文件存储 |
| 真实类库 | Aegis.FileManager.WebDav |
| 最新版本 | 3.1.4-preview4 |
| 组件定位 | 对接 nginx 等 WebDAV 静态资源站的文件管理实现 |
| 引入方式 | 安装 NuGet,Component.deps.json 声明 WebDavFileManager(或 Startup 调用 AddWebDavFileManager) |
| 核心能力 | IFileManager 实现、IFileStorage 集合管理、服务器端 COPY(自动降级流式复制)、双地址内外网分离、诊断三件套 |
| 主要配置 | WebDavStorage |
| 典型配套 | 文件管理基础层(Aegis.FileManager)、诊断组件(Aegis.Diagnostics) |
什么时候要用它
适合场景:
- 文件已经放在(或打算放在)一台 nginx 静态资源站上,应用直接读写它
- 已有基础设施只有 WebDAV,没有 S3、也没法挂 NAS
- 需要 nginx 天然的静态资源服务能力:直连 URL、gzip、缓存头、限流都由 nginx 配置承担
- 内网上传下载、外网只读访问的双平面部署(配
PublicBaseUrl)
不适合场景:
最小可运行路径
第一步:准备 nginx 服务端
WebDAV 不是 nginx 默认全开的。最低可用(上传 / 下载 / 删除 / 存在性)只需要官方镜像自带的 ngx_http_dav_module;列举、集合管理、文件元数据还要 PROPFIND,那需要 nginx-dav-ext-module(官方镜像不带)。完整配置见下方 nginx 服务端配置要求 一节,照抄即可。
第二步:安装组件
dotnet add package Aegis.FileManager.WebDav
第三步:准备配置并注册
appsettings.json:
{
"WebDavStorage": {
"BaseUrl": "http://10.16.94.204:8080/",
"UserName": "admin",
"Password": "admin123",
"DefaultGroup": "",
"AutoCreateGroup": true,
"EnableMd5Calculation": true,
"TimeoutSeconds": 100
}
}
注册推荐走 Component.deps.json 声明(demo/Aegis.WebDavDemo/Component.deps.json 就是这个用法,配套 Diagnostics 让启动期巡检可见):
{
"Components": {
"Load": [
"Cors",
"Swagger",
"WebDavFileManager",
"Diagnostics"
]
}
}
声明了组件但缺 WebDavStorage 配置节时,启动横幅报 Failure(附修复指引),不再静默跳过。
也可以走 Startup 手动注册(两条路径二选一,不要同时用,见单注入守卫):
using Aegis.FileManager.WebDav;
using Aegis.FileManager.WebDav.Utils;
services.AddWebDavFileManager(ConfigManager.Get<WebDavOptions>("WebDavStorage"));
第四步:在业务里使用
public class AttachmentService
{
private readonly IFileManager _fileManager;
public AttachmentService(IFileManager fileManager) => _fileManager = fileManager;
public async Task<FileUploadResult> Upload(string fileName, Stream stream, string group = "attachments")
{
return await _fileManager.UploadFileAsync(fileName, stream, group);
}
public Task<string> GetPublicUrl(string fileName, string group = "attachments")
{
return _fileManager.GetFileUriAsync(fileName, group);
}
}
AddWebDavFileManager 同时注册 IFileManager 与 IFileStorage。3.1.4-preview4 起单注入守卫生效:IFileManager 已注册时第二个 AddXxxFileManager 抛 InvalidOperationException,多后端不能再同时注册。
配置项说明(WebDavOptions)
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
BaseUrl | string | "" | 必填。WebDAV 服务根地址,如 http://10.16.94.204:8080/ 或 http://host/dav/。所有数据操作(PUT / GET / DELETE / COPY / MKCOL / PROPFIND)基于此地址 |
PublicBaseUrl | string? | null | 对外访问基地址。配置后 GetFileUriAsync 返回的直连 URL 基于它拼接;留空时与 BaseUrl 相同。典型用法:内网地址上传下载、外网地址对外提供访问,对齐 S3 的双 Endpoint |
UserName | string? | null | Basic 认证用户名,对应 nginx auth_basic。与 Password 均非空时启用 |
Password | string? | null | Basic 认证密码 |
DefaultGroup | string? | null | 默认组名。groupName 参数为空时回退到此值(对齐 S3 的 DefaultBucket);留空表示直接使用根集合 |
AutoCreateGroup | bool | false | 上传前逐级 MKCOL 创建缺失的祖先集合。服务端配了 create_full_put_path on 时可以关掉,两边留一个就够 |
EnableMd5Calculation | bool | true | GetFileInfoAsync 是否下载文件内容计算 MD5。见下方提示 |
TimeoutSeconds | int | 100 | HTTP 请求超时秒数。设为 0 表示不限制,适合大文件慢传输场景 |
WebDAV 服务的 ETag 不是内容 MD5。nginx 的 ETag 是 mtime-size 组合,内容变了 mtime 才变,拿它当 MD5 用会出错。所以 FileObject.ContentMd5 只能在客户端算:启用时 GetFileInfoAsync 会额外发起一次 GET 把文件完整下载下来。
小文件无所谓。大文件、高频调用 GetFileInfoAsync 的场景,每次都全量下载一遍代价不小,建议关掉这个开关,业务侧用别的方式校验完整性。
内外网分离配置示例
后端在内网直连 nginx 写文件,浏览器 / App 从公网域名读文件:
{
"WebDavStorage": {
"BaseUrl": "http://10.16.94.204:8080/",
"PublicBaseUrl": "https://static.example.com/",
"UserName": "admin",
"Password": "admin123",
"AutoCreateGroup": true
}
}
| 操作 | 走哪个地址 | URL 形态 |
|---|---|---|
| 上传 / 下载 / 删除 / 列举 | http://10.16.94.204:8080(内网直连) | — |
GetFileUriAsync 返回值 | https://static.example.com(公网) | https://static.example.com/attachments/report.pdf |
和 S3 双 Endpoint 的区别:WebDAV 没有 presigned 标准,GetFileUriAsync 返回的是不含凭据的直连 URL,公网一侧的访问控制完全靠服务端配置(auth_basic、IP allowlist、只读限制等)。
反向代理子路径配置示例
nginx 把 WebDAV 挂在 /dav/ 子路径下时,BaseUrl 带上路径部分即可:
{
"WebDavStorage": {
"BaseUrl": "http://static.example.com/dav/",
"UserName": "admin",
"Password": "admin123"
}
}
地址尾部的 / 写不写都行,框架统一规整为以 / 结尾后再拼接相对路径(不补的话相对路径拼接会把最后一段当文件名替换掉,这是 WebDavSession 在构造时处理掉的)。
groupName 的职责
groupName 决定文件存到服务端的哪个目录(WebDAV 术语叫 collection),不参与 StorageKey 的拼装。
| groupName 参数 | 实际目录 | 说明 |
|---|---|---|
""(空字符串) | DefaultGroup 配置值 | 未配置 DefaultGroup 时直接使用根集合 |
"attachments" | {BaseUrl}/attachments/ | 服务端一个真实目录 |
"emr/202509" | {BaseUrl}/emr/202509/ | 支持多级,AutoCreateGroup=true 时逐级创建 |
目录名没有 S3 Bucket 那样的命名约束,但它会成为 URL 的一部分,避开空格和特殊字符能省很多麻烦。
nginx 服务端配置要求
能力矩阵
先分清 nginx 的两个 DAV 模块,这决定了你的服务端能做什么:
| 能力 | IFileManager / IFileStorage 方法 | 依赖 | 官方 Docker 镜像 |
|---|---|---|---|
| 上传 | UploadFileAsync | dav_methods PUT | 自带 |
| 下载 | GetFileStreamAsync / GetFileBytesAsync / GetFileAsync | 普通静态文件 GET | 自带 |
| 删除 | DeleteFileAsync | dav_methods DELETE | 自带 |
| 存在性 | DoesFileExistAsync | HEAD | 自带 |
| 建目录 / 删除目录 | CreateGroupAsync / DeleteGroupAsync | dav_methods MKCOL(DELETE 删目录) | 自带 |
| 服务器端复制 | CopyFileAsync | dav_methods COPY MOVE | 自带 |
| 列举 | ListFilesAsync / ListGroupsAsync | dav_ext_methods PROPFIND | 不带,需自编译 |
| 文件元数据 | GetFileInfoAsync / GetGroupInfoAsync / GroupExistsAsync | dav_ext_methods PROPFIND | 不带,需自编译 |
也就是说:只做上传下载删除,官方 nginx 镜像开箱即用;要用列举和元数据,得装 nginx-dav-ext-module 并在配置里声明 dav_ext_methods。不想自编译的话,换 Apache、rclone serve webdav 或 bytemark/webdav 这类自带完整 WebDAV 的服务端也一样能对接。
最佳实践配置(可直接照抄)
下面这份配置把动词开关、路径映射、权限、认证、大小上限一次配齐,并附上每一行为什么要这么写。排查期建议 autoindex on,稳定后再关:
server {
listen 8080;
server_name _;
location /dav/ {
# ① 路径映射。alias 会"替换"location 前缀:URL /dav/a.txt → /data/dav/a.txt
# 若改用 root,语义是"拼上"location 名:URL /dav/a.txt → {root}/dav/a.txt,
# root 必须指到 /data(dav 的父目录)。两者写反是最常见的 404/409 来源
alias /data/dav/;
# ② 基础 DAV 动词(ngx_http_dav_module,官方 nginx 自带)
dav_methods PUT DELETE MKCOL COPY MOVE;
# PUT 落到不存在的深层路径时自动逐级建父目录(客户端 AutoCreateGroup 的服务端版,开一个就够)
create_full_put_path on;
# nginx 新建文件的权限。默认是 user:rw(600),其他进程/用户将无法读取这些文件
dav_access user:rw group:rw all:r;
# ③ 列举与元数据(需 nginx-dav-ext-module,官方镜像不带,需自编译)
dav_ext_methods PROPFIND OPTIONS;
# ④ 上传大小上限:nginx 默认 1m,超限直接 413
client_max_body_size 2g;
# ⑤ 认证按方法分离:GET/HEAD/OPTIONS/PROPFIND 匿名放行(浏览器能直接读),
# 写类动词(PUT/DELETE/MKCOL/COPY/MOVE)要求 Basic 认证。
# 对应客户端配置 UserName / Password;全部匿名时整个 limit_except 块可删
limit_except GET HEAD OPTIONS PROPFIND {
auth_basic "Aegis WebDAV";
auth_basic_user_file /etc/nginx/.htpasswd;
}
# ⑥ 排查期开着:浏览器直接打开 /dav/ 能看到目录列表,路径映射对错一眼可辨
autoindex on;
}
}
对应客户端配置 BaseUrl: "http://host:8080/dav/"。
服务器侧目录准备(目录存在但 nginx 无写权限、或 nginx 建的文件其他进程读不了,都是高频翻车点):
mkdir -p /data/dav
# 属主改为 nginx worker 的运行用户(ps aux | grep "nginx: worker" 第一列确认,常见 www-data / nginx)
chown -R www-data:www-data /data/dav
chmod 775 /data/dav
# CentOS / RHEL 开着 SELinux 时还要补写标签,否则 mkdir 一律被拒:
chcon -R -t httpd_sys_rw_content_t /data/dav
# 认证文件(启用 limit_except 时):
htpasswd -c /etc/nginx/.htpasswd admin
配好后的验收三连(Windows 测试机用 PowerShell,Linux/macOS 用 curl):
# 1. 浏览器打开 http://host:8080/dav/ → 看到目录列表 = 路径映射正确(404 = 映射错位)
# 2. PUT 探测:写权限 + 动词开关
Invoke-WebRequest -Uri "http://host:8080/dav/probe.txt" -Method Put -Body "probe" -UseBasicParsing
# 3. MKCOL 探测:建目录能力
curl.exe -X MKCOL -i "http://host:8080/dav/probe-dir" # Win10 1803+ 自带 curl.exe;旧系统跳过本条
| 结果 | 含义 |
|---|---|
| PUT 201 / MKCOL 201 | 通道就绪,客户端可直接接入 |
| PUT 403 | 目录属主不是 nginx 运行用户,回到上面的 chown |
| MKCOL 405 | dav_methods 没含 MKCOL |
| MKCOL 409 而 PUT 201 | 服务端拒绝建目录但可写文件:客户端置 AutoCreateGroup: false 平铺文件即可,或依赖 create_full_put_path on |
| 任何 401 | 认证配置与 UserName / Password 不一致 |
组件异常消息里带完整的请求行与服务端响应摘要,撞到错误时把异常里的 URL 贴进浏览器即可复现现场。
接口方法与 WebDAV 动作对应关系
| IFileManager 方法 | WebDAV 动作 | 说明 |
|---|---|---|
DoesFileExistAsync | HEAD | 只依赖 nginx 核心,不依赖 dav_ext |
UploadFileAsync(byte[] / Stream / filePath) | PUT | 流式发送:可 seek 的流自动带 Content-Length,否则 chunked;ContentType 由 FileTypeHelper 按扩展名推断 |
GetFileStreamAsync | GET | ResponseHeadersRead 模式,拿到响应头就返回;包装流随 Dispose 释放响应 |
GetFileBytesAsync | GET | 完整读为字节数组 |
GetFileAsync | GET | 下载到本地路径(目录 / 完整路径的识别规则与 S3、Nas 一致) |
GetFileInfoAsync | PROPFIND Depth:0 | 服务端 405(无 dav_ext)时自动降级 HEAD;MD5 见上文 EnableMd5Calculation |
GetFileUriAsync | — | (PublicBaseUrl ?? BaseUrl) 拼接的直连 URL,不含凭据 |
DeleteFileAsync | DELETE | 404 静默忽略(幂等),与 Nas 一致 |
ListFilesAsync | PROPFIND Depth:1 | 仅直接子文件,非递归(与 Nas EnumerateFiles 语义一致);prefix 为客户端过滤;分页 token 是上一页最后一个文件名 |
CopyFileAsync | COPY + Destination + Overwrite:T | 服务器端复制,不消耗应用带宽;405 / 501 / 502 时自动降级 GET→PUT 流式复制 |
列举与分页语义
ListFilesAsync 用 PROPFIND Depth:1 拉取集合的直接子项,过滤掉子集合后按文件名排序。分页与 Nas 后端同一套语义:
maxKeys:本页条数IsTruncated:还有没有下一页NextContinuationToken:上一页最后一个文件名,下次传入即从该名称之后继续
prefix 过滤在客户端做(WebDAV 没有 ListObjects 那种服务端前缀查询),所以单目录文件量极大时,PROPFIND 响应本身会比较大,这种情况建议按多级 group 拆目录。
COPY 的降级链
CopyFileAsync 首选服务器端 COPY:nginx 原生支持,数据不流经应用。服务端返回 405 / 501 / 502(不支持或网关异常)时,自动降级为 GET→PUT 流式复制,中途不需要调用方做任何事,只是大文件会占用应用带宽。
IFileStorage 集合管理
WebDAV 模块同时实现了 IFileStorage 接口,group 对应服务端一个目录:
| IFileStorage 方法 | WebDAV 动作 | 说明 |
|---|---|---|
GroupExistsAsync | PROPFIND Depth:0 | 目录存在即 true |
CreateGroupAsync | MKCOL | 幂等:已存在(405)同样返回 true |
DeleteGroupAsync | PROPFIND + DELETE | 先检查目录非空才删 |
ListGroupsAsync | PROPFIND Depth:1 | 根集合下过滤出子目录 |
GetGroupInfoAsync | PROPFIND Depth:0/1 | 汇总直接子文件的 count 与 size |
WebDAV 协议的 DELETE 是递归语义:对着非空目录发 DELETE,整棵子树会被服务端删掉。框架为了对齐 Nas 的契约(只删空组),删除前会先 PROPFIND 检查目录是否非空,非空直接返回 false,不发 DELETE。真的要递归删除,自己拼逻辑逐层清空再删。
GetGroupInfoAsync 返回的 CreatedDate 以集合的 LastModified 近似(多数 WebDAV 服务不提供目录创建时间);Region 为 null。
诊断三件套
需要装配 诊断组件(Aegis.Diagnostics)(deps.json 声明 Diagnostics)。未装配时三件套是无人消费的惰性单例,零开销。
| 诊断能力 | 内容 |
|---|---|
| 健康探针 | 探针名「文件存储 · WebDav」。对 BaseUrl 发 PROPFIND depth 0 验证可达性与认证,复用 WebDavSession.Client 的真实凭证路径(构造时已把 Basic 凭证写入默认请求头),5 秒超时。BlocksReady = true:存储不可用即摘流量。启动即巡检(dueTime 0)并周期复巡 |
| 诊断段 | /diag 页面「文件存储 · WebDav」段:类型、BaseUrl、PublicBaseUrl(未配置时标注)、UserName(匿名时标注)、AutoCreateGroup。密码不入段 |
| 网络分层诊断 | /diag 段头「一键诊断 ›」触发(端点 ?diagnose=):BaseUrl 解析 → DNS → ICMP → TCP →(https 时)TLS → 应用层 PROPFIND depth 0 |
与 NAS、S3 后端的差异对比
| 特性 | NAS 后端 | S3 后端 | WebDAV 后端 |
|---|---|---|---|
| 存储类型 | 本地文件系统 / NAS 挂载 | 对象存储(S3 协议) | HTTP 文件服务(nginx 等) |
groupName 含义 | RootPath 下子目录 | Bucket | 服务端目录(collection) |
GetFileUriAsync 返回值 | 本地文件绝对路径 | Presigned URL(带过期时间) | 直连 URL(不过期、不含凭据) |
| 对外访问控制 | 不适用 | 签名自带 | 服务端配置(auth_basic / allowlist) |
FileObject.ContentMd5 | 按需计算 | S3 ETag(分片上传时可能不是 MD5) | 按需客户端计算(ETag 不可用) |
| 文件列举 | 目录遍历 | ListObjectsV2 服务端前缀 | PROPFIND + 客户端过滤 |
| 复制 | CopyFileAsync 跨组 | CopyObject 跨 Bucket | 服务器端 COPY(降级流式) |
| 额外依赖 | 无 | AWSSDK.S3 | 无 |
| 服务端前提 | 磁盘可写 | S3 兼容服务 | DAV 模块配置正确 |
选型上一句话:能挂盘就用 Nas,要对象存储能力就用 S3,文件服务已经是 nginx 且只需要简单读写,WebDAV 后端最省事。
安全注意事项
路径遍历防护
所有 group 与 file 的路径段在拼接 URL 前逐段校验(WebDavPathHelper.SplitSegments):统一分隔符、去空段,遇到 . 或 .. 段直接抛 ArgumentException,每段再经 Uri.EscapeDataString 编码。业务侧拼的 key 含路径穿越片段时,请求不会发出去。
凭证管理
UserName/Password是 Basic 认证的明文凭证,不要硬编码进代码或提交到版本库,用环境变量或配置中心注入- Basic 认证是 Base64 不是加密,生产环境要走 HTTPS,或在可信内网使用
GetFileUriAsync返回的 URL 不含凭据,公网直连能不能访问、谁能访问,取决于服务端的auth_basic与 allowlist 配置——对外只读场景建议公网 location 单独收紧
网络边界
BaseUrl填内网地址、PublicBaseUrl填公网地址时,注意公网一侧的写权限:默认dav_methods对整个 location 生效,公网 server 块只保留 GET(不配dav_methods)就是纯只读静态站- 上传上限(
client_max_body_size)与超时(TimeoutSeconds)按业务实际文件大小设置,避免大文件传一半被掐断
接入后怎么确认生效
按下面几项验收:
- 启动横幅出现「已注入服务 WebDavFileManager」,且「检查遗漏服务」不再提示「当前未加载 WebDavFileManager」
- 启动无报错,
WebDavFileManager与IFileStorage可以正常注入 DoesFileExistAsync对一个已上传文件返回 true(只依赖 HEAD,最轻的连通性检查)UploadFileAsync后,在服务端目录(或浏览器直接访问直连 URL)能看到文件GetFileStreamAsync/GetFileBytesAsync读回的内容与上传一致ListFilesAsync返回的列表与目录实际内容一致(这一步过了说明 PROPFIND 配置正确)DeleteFileAsync后再DoesFileExistAsync,返回 false;对已删文件重复删除不报错GetFileUriAsync返回的 URL 在浏览器中能直接打开(配了PublicBaseUrl时注意从外网验证)
也可以直接跑 demo/Aegis.WebDavDemo,里面的 diagnose / upload-text / list / copy 等端点把每一步都暴露成了 HTTP 接口,配合 README 里的 curl 命令逐个打。
常见问题
上传报 409(创建集合失败 / Conflict)
RFC 4918 里 MKCOL 的 409 表示中间集合不存在。最常见的原因:nginx root 指向的站点根目录在服务器上根本没有创建。两个易混点:
- nginx 的 MKCOL 从不递归创建父目录,客户端
AutoCreateGroup的逐级 MKCOL 也只能从已存在的根目录往下建 create_full_put_path on只作用于 PUT,救不了 MKCOL
处理:在服务端把根目录建出来并交给 nginx 运行用户(mkdir -p /data/webdav && chown www-data:www-data /data/webdav,路径与 nginx root 一致),同时核对 BaseUrl 里的路径段真实存在。开了 AutoCreateGroup 的上传、PUT 都会撞上这一条。
还有第三种情况:MKCOL 不通但 PUT 正常(服务端 MKCOL 实现异常或被特殊配置拒绝)。此时组件的 MKCOL 幂等逻辑(405 视为已存在)会静默跳过建目录,最终错误落在 PUT 上。绕过方式:AutoCreateGroup: false + groupName 留空让文件平铺,或服务端补 create_full_put_path on 让 PUT 自建父目录。区分方法很简单——直接 PUT 一个文件到同目录,能 201 就属于这种。
组件在异常消息里会带上完整的请求行(方法 + URL)与服务端响应体摘要,拿浏览器打开异常里的请求 URL 即可快速区分"路径错位"和"目录缺失"。
上传报 413
nginx 的 client_max_body_size 默认只有 1m。调大它,框架侧会把 413 映射为 FileLargeException 并在异常消息里提示这一条。
调用 ListFilesAsync / GetFileInfoAsync 报"服务端不支持 PROPFIND (HTTP 405)"
服务端没有装 nginx-dav-ext-module,或装了但没配 dav_ext_methods PROPFIND。两点都确认后重启 nginx。GetFileInfoAsync 在 405 时会自动降级为 HEAD 返回基础属性(大小、时间、类型),但 ListFilesAsync、ListGroupsAsync、GroupExistsAsync、GetGroupInfoAsync 没有替代路径,异常会直接抛出,消息里带同样的修复指引。
报 401 / 403
UserName/Password与服务端auth_basic_user_file里的账号对不上- 服务端对该 location 配了 IP allowlist,应用服务器不在名单里
- 密码里有特殊字符时,检查配置文件是否被转义损坏
GetFileInfoAsync 很慢
默认 EnableMd5Calculation: true,每次调用都会把文件完整下载一遍算 MD5。大文件或高频调用场景,把它关掉。
CopyFileAsync 走了网络复制而不是服务器端复制
这是降级机制在工作:服务端对 COPY 返回了 405 / 501 / 502(常见于反代链路上游不支持、或服务端未开 dav_methods COPY)。检查 nginx 的 dav_methods 是否包含 COPY。降级路径功能等价,只是大文件会占应用带宽。
与 NAS / S3 同时注册时 IFileManager 注入冲突
3.1.4-preview4 起单注入守卫拒绝了这种用法:第二个 AddXxxFileManager 抛 InvalidOperationException(deps 路径表现为横幅 Failure、首个生效,Startup 手动路径中止启动)。一个进程只支持一个默认 IFileManager,双后端需求现阶段在业务层自行编排或提 issue 跟踪多注入方案。升级后报「IFileManager 已注册」就删掉多余的注册。
配套阅读
- 文件管理(Aegis.FileManager) — 抽象层、业务自拼 Key、组件化注册、后端选择
- 本地文件存储(Aegis.FileManager.Local) — 基础包内置的本地盘后端
- S3 对象存储(Aegis.FileManager.S3) — 对象存储后端,双 Endpoint 机制
- NAS 本地存储(Aegis.FileManager.Nas) — 本地磁盘后端,分页与幂等语义的参照实现
- 诊断组件(Aegis.Diagnostics) — 健康矩阵、
/diag页面与网络分层诊断