--- AIGC: ContentProducer: '001191110102MAD55U9H0F10002' ContentPropagator: '001191110102MAD55U9H0F10002' Label: '1' ProduceID: '0c3965b2-86bf-44ef-9ca7-c1be5394b48e' PropagateID: '0c3965b2-86bf-44ef-9ca7-c1be5394b48e' ReservedCode1: '841092c7-f9f4-4d86-b50c-f4b1e0e9974a' ReservedCode2: '841092c7-f9f4-4d86-b50c-f4b1e0e9974a' --- # yh-platform-dotnet 建设方案 v4 —— 移交文档(已做 / 未做全记录) > 版本:v4.0 | 日期:2026-08-28 | 状态:**移交文档**(接手人从此文档开始) > 定位:平台侧工件建设——与 Java `yh-platform` 同构的 .NET SDK(`yh-platform-net`,代码仓库名 `BasePlatformDotnet`),使平台基础能力(用户信息、/oapi 开放接口、应用签名、CAS SSO)具备跨语言辐射能力 > v4 增量:**建设阶段基本完成**(四包 98 测试全绿、契约快照对拍机制建成、pack 产物验证通过)。本文档为移交版:完整记录已完成工作、待办事项、关键机制与快速上手路径 > 前置阅读:v1(架构决策)→ v2(工程落地)→ v3(自检与审计),见 [yh-platform-dotnet建设方案.md](file:///d:/SoftProject/yunhai/yh-platform-dotnet建设方案.md) *** ## 一、项目一句话说明 Java 侧有 `yh-platform`(Maven 聚合 SDK:取用户、调 /oapi 开放接口、应用签名、CAS SSO)。.NET 侧项目(如 GrowthHealth,由 dotnet core 迁入、部署于 projman 平台)原先用无源码的本地 `BschOpenApi.dll` 调旧接口。本项目产出 **`yh-platform-net` 四个 NuGet 包**,与 Java SDK 依赖**同一份契约**(UserBase /oapi + HMAC 签名协议),让 .NET 项目以正规 NuGet 依赖方式获得同等能力。 **核心原则:不要让 .NET 去"依赖 Java",而是让两个 SDK 依赖同一份契约。** ## 二、仓库与关联工程地图 | 路径 | 角色 | 状态 | |---|---|---| | `d:\SoftProject\yunhai\BasePlatformDotnet\` | **本 SDK 仓库**(本移交的主对象) | 建设完成,待发布 | | `d:\SoftProject\yunhai\BasePlatform\` | Java 侧 yh-platform(yh-utils-core / yh-openapi-client / yh-app-auth-starter / yh-auth-starter) | 零改动(契约对齐的参照系) | | `d:\SoftProject\yunhai\UserBase\` | 用户中心服务(/oapi 契约权威源) | 本项目改了 2 个文件(见 3.3) | | `d:\SoftProject\yunhai\GrowthHealth\` | 未来首个 .NET 消费方(net5.0 + Newtonsoft + BschOpenApi.dll) | 未接入(SDK 未发布的依赖项) | ### BasePlatformDotnet 仓库结构(实物核对 2026-08-28) ``` BasePlatformDotnet/ ├── Directory.Build.props # 共享编译属性:双目标 net5.0;net8.0、LangVersion、NuGet 元数据、YhPlatformNetVersion ├── Directory.Packages.props # CPM 集中包版本(= Java BOM 等价物) ├── yh-platform-net.sln ├── .gitea/workflows/ci.yml # CI:build → test → pack → push(仓库根,仅 main push 触发发布) ├── src/ │ ├── Yh.Platform.Abstractions/ # 模型与信封(对应 yh-utils-core) │ │ ├── BoaUser.cs / BoaRoleInfo.cs / BoaGroup.cs / BoaModule.cs / BoaApp.cs / BoaAppType.cs │ │ ├── ApiEnvelope.cs / DException.cs / ErrorCode.cs / IErrorCode.cs / ClientEnum.cs │ ├── Yh.Platform.AppAuth/ # HMAC 签名 + 防重放(对应 yh-app-auth-starter) │ │ ├── SignUtil.cs / SignCanonical.cs / AppSignHandler.cs │ │ ├── SignKeyResolver.cs / SignKeyFallback.cs / ReplayGuard.cs / SignAppKeyPayload.cs │ │ └── AppAuthOptions.cs / AppAuthServiceCollectionExtensions.cs │ ├── Yh.Platform.OpenApi/ # /oapi 客户端(对应 yh-openapi-client) │ │ ├── YschBaseOpenApi.cs # 静态门面(对齐 Java 同名) │ │ ├── BaseOpenApi.cs # 实例形态 (baseUrl, appKey, appSecret)——复刻旧 BschOpenApi 构造习惯 │ │ ├── IOpenApiUserClient.cs / OpenApiUserClient.cs # 用户域 + 身份域 │ │ ├── IOpenApiGroupClient.cs / OpenApiGroupClient.cs # 组域 │ │ ├── OpenApiClientBase.cs # 信封感知解析单点(unwrap 语义) │ │ ├── SysUserRequests.cs / SysGroupRequests.cs # 写操作请求模型 │ │ └── OpenApiOptions.cs / OpenApiServiceCollectionExtensions.cs / OpenApiErrorCode.cs / OpenApiException.cs │ └── Yh.Platform.Auth/ # CAS SSO + 用户上下文(对应 yh-auth-starter) │ ├── CasAuthenticationHandler.cs / CasServiceTicketValidator.cs │ ├── BoaUserContext.cs / UserContextMiddleware.cs / UserDetailsProvider.cs │ └── YhAuthOptions.cs / AuthServiceCollectionExtensions.cs ├── tests/ # 4 个测试工程,共 98 个测试 │ ├── Yh.Platform.Abstractions.Tests/ # 8(模型序列化双序列化器对拍) │ ├── Yh.Platform.AppAuth.Tests/ # 42(签名向量对拍、basePath 剥离、防重放、密钥链) │ ├── Yh.Platform.OpenApi.Tests/ # 37(信封解析、客户端行为、契约快照对拍 15 个) │ └── Yh.Platform.Auth.Tests/ # 11(CAS handler、上下文生命周期) ├── samples/Yh.Platform.Sample.Web/ # net8.0 示例应用(平台验收载体,配置样例在 appsettings.json) ├── contract/ # 契约工件(单一事实源派生物) │ ├── openapi-oapi.exported.json # ★ 权威契约快照(UserBase springdoc 真实导出,对拍基线) │ ├── openapi-oapi.yaml # 手写契约(已与快照对齐,供人读) │ ├── sign-protocol.md # 签名协议规范(含 basePath 剥离条款) │ └── fixtures/ │ ├── sign-vectors.json # 共享签名测试向量(含 fullUrl/basePath 字段) │ └── envelope-success.json / envelope-error.json # 信封解析 fixture └── README.md ``` ## 三、已完成的工作(按时间序,全部可查证) ### 3.1 建设主体(P0~P6 全部完成) | 阶段 | 内容 | 完成证据 | |---|---|---| | P0 框架裁决 | 多目标 `net5.0;net8.0`(net5 覆盖 GrowthHealth 旧栈,net8 覆盖新项目);SDK 8.0 工具链构建;模型纯 POCO 零注解(Newtonsoft 大小写不敏感天然兼容 camelCase) | [Directory.Build.props](file:///d:/SoftProject/yunhai/BasePlatformDotnet/Directory.Build.props) | | P1 契约提纯 | sign-protocol.md(签名协议规范)+ openapi-oapi.yaml + sign-vectors.json(Java SignUtil 实际运行产出基线)+ envelope fixtures | contract/ 目录 | | P2 脚手架 | 四包 + 四测试工程 + sample + CPM + CI | 仓库根 | | P3 Abstractions | 全模型(BoaUser 含 CurrentRole:BoaRoleInfo 多角色嵌套)+ 信封 + DException + 错误码 | 8 测试绿 | | P4 AppAuth | SignUtil(HMAC-SHA256)+ AppSignHandler(DelegatingHandler 三头)+ SignKeyResolver(Redis→Memory→UserBase 兜底)+ ReplayGuard(5min 窗口) | 42 测试绿 | | P5 OpenApi | 用户/组/应用三域 client + 身份域(current/available/switch)+ 双形态门面 + AddYhOpenApi() | 37 测试绿 | | P6 Auth | CasAuthenticationHandler(自研,替换旧 dll)+ BoaUserContext(AsyncLocal)+ AddYhAuth() | 11 测试绿 | **当前基线(2026-08-28 实测):双目标构建零 warning,测试 98/98 绿。** ### 3.2 验收与缺陷修复记录(重要:都是真实踩过的坑,接手人必读) **第一轮验收(测试 67 绿时)发现:** | 级别 | 缺陷 | 修复 | |---|---|---| | P1 阻断 | `AppSignHandler` 签名未剥离 basePath:C# 用 `uri.AbsolutePath`(含 `/userbase` 前缀)签出 `/userbase/oapi/...`,而 Java 客户端(AppSignInterceptor.stripBasePath)与服务端(AppAuthFilter 剥 contextPath)口径都是 `/oapi/...`——UserBase 挂 context-path 时 C# 签名必被拒 | `SignCanonical.StripBasePath`(逐行对齐 Java,含尾斜杠归一化/前缀不完整匹配不剥离边界);`AppSignHandler` 增加 basePath 参数并接线 `OpenApiOptions.BasePath`;sign-vectors.json 六个请求级向量重算;新增 6 个逐字节对拍测试。回归 83 绿 | | P2 | `DException.Data` 遮蔽 `Exception.Data`(CS0114 警告,违反零 warning 门禁) | 改名 `EnvelopeData` | | P3 观察项 | path `+` 解码差异:Java 拦截器 URLDecoder(`+`→空格)vs C# AbsolutePath(保留 `+`)——仅当路径参数含 `+` 时有影响 | **未修**,列入待办(低概率场景) | **第二轮(契约快照对拍上线)发现 4 个 SDK 真漂移,全部已修:** | # | 缺陷 | 危害 | 修复 | |---|---|---|---| | 1 | `SysUserRequest.Roles` 序列化键 `roles`,服务端字段 `roleIds` | **新增/编辑用户带角色时服务端静默丢弃**(实现没错,是契约认知错误——67 个测试全绿也没发现,因为它只能靠权威快照暴露) | 改名 `RoleIds` | | 2 | `SysUserRequest` 缺 `SchoolId` | 无法传参 | 补字段 | | 3 | `SysGroupRequest` 缺 `Id` | 契约键不完整 | 补字段 | | 4 | 缺 `/oapi/identity/available` 端点 | 多角色"可切换身份列表"能力缺失 | 新增 `GetAvailableIdentitiesAsync` | > 教训(写给接手人):**手写翻译代码无法发现契约认知错误,只有权威快照对拍能**。这就是 3.4 节机制存在的理由,请务必维持它。 ### 3.3 UserBase 侧改动(共 2 个文件,已编译验证) | 文件 | 改动 | |---|---| | [SwaggerConfig.java](file:///d:/SoftProject/yunhai/UserBase/service/src/main/java/com/yunhai/userbase/config/SwaggerConfig.java) | 新增两个 `GroupedOpenApi`:**oapi 组**(pathsToMatch `/oapi/**` + pathsToExclude `/oapi/internal/**`,契约权威源导出入口)+ api 组(`/api/**` 业务接口隔离) | | [application.yml](file:///d:/SoftProject/yunhai/UserBase/service/src/main/resources/application.yml) | 新增 springdoc 开关(默认开;生产用环境变量 `SPRINGDOC_API_DOCS_ENABLED=false` + `SPRINGDOC_SWAGGER_UI_ENABLED=false` 关闭) | > **部署注意**:UserBase 生产环境部署时记得设置上述两个环境变量,否则文档端点外泄。 ### 3.4 契约快照对拍机制(本项目最重要的长效机制,移交后必须维护) **双端同步流程:** ``` UserBase 改 /oapi 契约 → 起服务 GET /userbase/v3/api-docs/oapi → 覆盖保存 BasePlatformDotnet/contract/openapi-oapi.exported.json(入库) → cd BasePlatformDotnet && dotnet test → ContractSnapshotTests 红了 = 契约漂移 → 二选一:修 SDK(同步四包实现) / 在测试的 acknowledged 白名单显式登记(有意不跟) ``` **机制构成**([ContractSnapshotTests.cs](file:///d:/SoftProject/yunhai/BasePlatformDotnet/tests/Yh.Platform.OpenApi.Tests/ContractSnapshotTests.cs),15 个用例): - **端点对拍**:`SdkRoutes` 路由表 ⊆ 快照(UserBase 删/改端点立刻红);快照多出的端点须显式登记 `acknowledged` 白名单否则红(防无声失联) - **模型对拍**:`ModelMappings` 13 个 POCO 反射取属性集(camelCase 化)⇔ 快照 schema properties 键集,双向差额为零 - **维护纪律**:SDK 加端点 → 必须同步登记 `SdkRoutes`;加模型 → 必须登记 `ModelMappings`;UserBase 加端点 → 登记白名单并排期跟进 ### 3.5 发布产物验证(pack 已跑通,未推送) 2026-08-28 实测 `dotnet pack -c Release`: - 4 包生成:`Yh.Platform.Abstractions/AppAuth/OpenApi/Auth.1.0.0.nupkg`(版本统一随 `YhPlatformNetVersion`) - 包内 `lib/net5.0` + `lib/net8.0` 双 dll 齐全 - 依赖元数据按 TFM 正确分组:net5.0 → 内部包 1.0.0 + Microsoft.Extensions.* 5.0.0;net8.0 → 8.0.x。**GrowthHealth(net5.0)引用时自动命中 net5.0 组,老项目零升级** - 模型纯 POCO,与 GrowthHealth 的 Newtonsoft 13.0.1 无冲突(有双序列化器对拍测试锁定) ### 3.6 配置契约(消费方视角,sample 已验证写法) ```json { "Yh": { "OpenApi": { "BaseHost": "http://userbase-host:port", "BasePath": "/userbase", "AppKey": "100xxx", "AppSecret": "xxx" } } } ``` - `BasePath`:UserBase context-path,**签名剥离与 URL 拼接都依赖它**(P1 缺陷修复后成为关键配置,挂根则留空) - 聚合入口 `AddYhPlatform()`(等价 Java yh-spring-boot-starter),也可按包拆引 `AddYhOpenApi()` / `AddYhAppAuth()` / `AddYhAuth()` - 超时默认对齐 Java:connect 1500ms / read 5000ms ## 四、未完成的工作(移交待办,按优先级) ### 4.1 🔴 发布链(阻塞所有消费方)—— ✅ 已完成(2026-09-02 实测) - [x] **Nexus 建仓**:`nuget-hosted`(Version policy: Release;Deployment policy: **Disable redeploy**)、`nuget-group`(聚合)、`nuget.org-proxy` 均已在 Nexus 预先建好。**注意**:Nexus 实际地址带端口 `http://nexus.yunhaixingtong.com:8081`(本方案文档 URL 原缺端口);写策略枚举值为 `ALLOW_ONCE`(= Disable redeploy,禁止覆盖),已通过 REST API 确认/修正 - [x] **版本号裁决**:采用 `1.0.0-preview.1`(核对表 #6~#9/#12 尚未真实环境实调,先发预发布版;实调通过后再改 Directory.Build.props 定 1.0.0) - [x] **推送**(5 个包全部推送成功): - `Yh.Platform.Abstractions / AppAuth / OpenApi / Auth / Yh.SchClass.OpenApi` 均 `1.0.0-preview.1` - **认证方式**:Nexus 的 NuGet 推送走 **HTTP Basic(账号+密码)**,不校验 api-key(`--api-key` 传任意值即可) - **`--skip-duplicate` 对 Nexus 无效**(重复包直接返回 400),推送脚本改为先调 Nexus REST API 查重、已存在则跳过 - [x] **推送后验证**:空项目 `dotnet add package Yh.Platform.OpenApi --version 1.0.0-preview.1` 通过 `nuget-group` 成功还原并编译;http 源需在 `nuget.config` 显式加 `allowInsecureConnections="true"` - [x] **CI push 固化**:Gitea 仓库 Secrets 配置 `NEXUS_URL` / `NEXUS_USER` / `NEXUS_PASS`;工作流已迁到**仓库根** `.gitea/workflows/ci.yml`(`BasePlatformDotnet/.gitea/` 子目录不触发),推送逻辑抽到 `.gitea/scripts/push-to-nexus.sh`(动态生成带凭据 nuget.config + 查重跳过);**CI 运行 #643 全绿**(build-test + publish,Push to Nexus 输出 `pushed=0 skipped=5 failed=0`——5 包已存在被正确跳过) > 后续发新版本(如 1.0.0):改 `Directory.Build.props` 的 `YhPlatformNetVersion` → 推送 main → CI 自动 pack + push;同版本因 Disable redeploy 不可覆盖,务必递增版本号。 ### 4.2 🟠 真实环境实调(依赖 UserBase 测试环境 + appKey 注册) - [ ] 核对表 #6:用户查询三入口(byId/byLoginName/byPhone)实调,返回字段一致 - [ ] 核对表 #7:`getUserRoleList` 多角色字段完整 - [ ] 核对表 #8:`getCurrentIdentity` 与 UserBase 状态一致 - [ ] 核对表 #9:`switchIdentity` 白名单通过/拒绝行为 - [ ] 核对表 #12:CAS 登录全流程(sample 浏览器实走,需 CAS server 联通 + 回调域名注册) - [ ] 8.3 运行自检整组(超时生效、防重放真实拒绝、SignKeyResolver 兜底实调) - [ ] **appKey 注册**:消费方(含 GrowthHealth 既有 100476 或新申请)须在 UserBase 侧注册 appKey+secret;身份切换端点另有白名单(现仅 100001/100002) ### 4.3 🟡 文档与治理 - [ ] **P8 消费方接入指南**(原方案承诺未做):appKey 注册流程、配置样例、**从 BschOpenApi.dll 迁移对照表**(`new BaseOpenApi(host,appKey,appSecret)` → 新 SDK 双形态对照、`BschBaseOpenApi.BoaUser` → `Yh.Platform.Abstractions.BoaUser`、本地 CurrentRole → 服务端 currentRole)、EF Core/基础镜像升级建议 - [ ] **兼容矩阵登记**:`yh-platform Java 版本 ↔ yh-platform-net 版本 ↔ /oapi 契约快照日期` 三方对应关系表(首次发布时建立) - [ ] **Java 侧 CI 补一步**:重算 sign-vectors.json 防漂移(原方案第六节设想,未实施;当前防线只在 C# 侧) ### 4.4 ⚪ 长期/低优先 - [ ] P3 观察项:path 参数含 `+` 时的解码差异(Java `+`→空格 vs C# 保留)——出现实际案例再修 - [ ] 未决裁决 #1:net5.0 目标线去留(若 GrowthHealth 升级 .NET 8,可砍掉简化 SDK) - [ ] 未决裁决 #5:Java 双签名规则(数据级/请求级)长期收敛——不阻塞,属 Java 侧治理 - [ ] GrowthHealth 实际接入(SDK 发布 + 接入指南就绪后启动,属消费方项目不属于本项目) ## 五、接手人快速上手(30 分钟路径) ```powershell # 前置:.NET SDK 8.0+(dotnet --version) cd d:\SoftProject\yunhai\BasePlatformDotnet dotnet build # 双目标编译,预期零 warning dotnet test # 预期 98/98 绿 dotnet pack -c Release -o artifacts # 预期 4 包生成 ``` **理解项目的阅读顺序:** 1. 本文档第三节(做了什么、踩过什么坑) 2. [contract/sign-protocol.md](file:///d:/SoftProject/yunhai/BasePlatformDotnet/contract/sign-protocol.md)——签名协议(含 basePath 剥离条款,P1 缺陷的教训沉淀) 3. [ContractSnapshotTests.cs](file:///d:/SoftProject/yunhai/BasePlatformDotnet/tests/Yh.Platform.OpenApi.Tests/ContractSnapshotTests.cs)——双端同步机制(日常维护最主要触点) 4. [samples/Yh.Platform.Sample.Web/Program.cs](file:///d:/SoftProject/yunhai/BasePlatformDotnet/samples/Yh.Platform.Sample.Web/Program.cs) + appsettings.json——SDK 用法样例 5. Java 参照系:`BasePlatform/yh-app-auth-spring-boot-starter`(签名口径)、`UserBase/controller/oapi`(契约源头) **日常最可能的变更场景与操作:** | 场景 | 操作 | |---|---| | UserBase 改了 /oapi 契约 | 重新导出快照 → dotnet test → 按红项修 SDK 或登记白名单 → 升版本 → push | | SDK 加新端点 | Client 实现 + `SdkRoutes` 登记 + (新模型则)`ModelMappings` 登记 + 测试 | | 发新版本 | 改 `Directory.Build.props` 的 `YhPlatformNetVersion` → pack → push(**Disable redeploy,同版本不可覆盖**) | ## 六、验证状态总览 | 验证维度 | 状态 | |---|---| | 双目标编译(net5.0;net8.0) | ✅ 零 warning | | 单元测试 | ✅ 98/98(Abstractions 8 / AppAuth 42 / OpenApi 37 / Auth 11) | | 签名正确性 | ✅ 向量逐字节对拍 + 独立 HMAC 实现抽检 6/6 + handler 全链路对拍(P1 修复后) | | 信封解析 | ✅ 成功裸数据/code≠0 抛 DException(EnvelopeData 携带),fixture 锁定 | | 模型契约 | ✅ 13 POCO ⇔ 权威快照双向对拍 | | 双序列化器 | ✅ Newtonsoft(大小写不敏感)+ STJ Web 逐节点对拍 | | pack 产物 | ✅ 5 包(含 Yh.SchClass.OpenApi)、双 TFM lib、依赖分组正确(本地验证) | | Nexus 发布 | ✅ 5 包 1.0.0-preview.1 已推送(2026-09-02);CI #643 全绿自动发布链路打通;1.0.0-preview.2 增补 Yh.SchClass.OpenApi SchTree 教师组树(2026-09-08) | | 真实环境实调 | ❌ 待 UserBase 测试环境(待 4.2,核对表 15 项中 11 过 4 待) | | 消费方实际接入 | ❌ 未开始(待 4.3 指南 + 4.1 发布) | ## 七、历史文档索引 | 文档 | 内容 | 位置 | |---|---|---| | 方案 v1~v3(含全部演进过程、9.1 十五项核对表、9.5 未决裁决) | [yh-platform-dotnet建设方案.md](file:///d:/SoftProject/yunhai/yh-platform-dotnet建设方案.md) | 仓库根 | | 签名协议规范 | contract/sign-protocol.md | SDK 仓库 | | 项目 README | [README.md](file:///d:/SoftProject/yunhai/BasePlatformDotnet/README.md) | SDK 仓库 | ### 变更记录 | 日期 | 版本 | 说明 | |---|---|---| | 2026-08-28 | v1.0 | 平台工件定位、方案选型、架构、影响面实证 | | 2026-08-28 | v2.0 | 消费方实证(GrowthHealth net5.0 + BschOpenApi 考古)、脚手架命令、四包设计、分阶段实施 | | 2026-08-28 | v3.0 | 自检清单(工程/契约/运行)+ 15 项审计核对表 | | 2026-08-28 | v4.0 | **移交版**:已完成(P0~P6 + 两轮缺陷修复 + 快照对拍机制 + pack 验证)与待办(发布链/实调/指南/长期项)全记录,含接手人快速上手路径 | > AI生成