Files
yunhai.netcore.tool/yh-platform-dotnet建设方案v4.md
T
wanghu 8896b29c4d
ci / build-test (push) Successful in 3m0s
ci / publish (push) Successful in 14m30s
feat: SchTree 客户端对接 SchGroup/GetTeaTree,升版 1.0.0-preview.2
- 补齐 4226b04 未入库的 3 个新文件(TeaTreeVo/SchClassSchTreeClient/契约测试),修复该提交编译不过
- 版本升至 1.0.0-preview.2:Nexus nuget-hosted 禁止重传(ALLOW_ONCE),同版本不递增会被查重跳过
2026-09-08 16:36:33 +08:00

269 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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生成