371 lines
43 KiB
Markdown
371 lines
43 KiB
Markdown
# yh-platform-dotnet 建设方案 v3 —— 从空文件夹到可发布 NuGet
|
||
|
||
> 版本:v3.0 | 日期:2026-08-28 | 状态:方案评审
|
||
> 定位:**平台侧工件建设** —— 建设与 Java `yh-platform` 同构的 .NET SDK(`yh-platform-net`),使平台基础能力(用户信息、/oapi 开放接口、应用签名、CAS SSO)具备跨语言辐射能力
|
||
> v2 增量:基于 GrowthHealth 既有 .NET 代码实证,给出可直接从空文件夹落地的工程实施方案(框架选型、仓库脚手架、命令序列、分阶段实施)
|
||
> v3 增量:把 v2 的多阶段门禁简化为**做完后一次性自检 + 功能逻辑审计核对**——建完后照单跑一遍,有问题修,没问题即收
|
||
|
||
***
|
||
|
||
## 一、消费方现状实证(决定 SDK 设计约束)
|
||
|
||
### 1.1 GrowthHealth 技术栈考古(2026-08-28 代码核查)
|
||
|
||
| 项 | 实证 | 对 SDK 设计的约束 |
|
||
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||
| 目标框架 | **net5.0**(五项目:Bsch.Web/Entity/Common/Repository/Service,[Bsch.Web.csproj:4](file:///d:/SoftProject/yunhai/GrowthHealth/service/Bsch.Web/Bsch.Web.csproj)) | SDK 最低必须可被 net5.0 消费 |
|
||
| JSON | **Newtonsoft.Json 13.0.1** + `AddNewtonsoftJson`(非 System.Text.Json) | 模型须 Newtonsoft 可反序列化(纯 POCO 即可,Newtonsoft 属性匹配大小写不敏感) |
|
||
| ORM | EF Core 5.0.9 + SqlServer + Dapper(本地 dll) | 与 SDK 无关,仅确认不冲突 |
|
||
| 旧平台 SDK | **BschOpenApi.dll**(本地 Library 引用,无 NuGet):`new BaseOpenApi(host, appKey, appSecret)`,命名空间 `BschBaseOpenApi`,类型 `BoaUser` / `CurrentRole`([ControllerAbstract.cs:18](file:///d:/SoftProject/yunhai/GrowthHealth/service/Bsch.Web/Controllers/ControllerAbstract.cs)、[SchClassService.cs:22](file:///d:/SoftProject/yunhai/GrowthHealth/service/Bsch.Service/SchClassService.cs)) | **yh-platform-net = BschOpenApi 的重生**。需提供同构构造函数形态 `(host, appKey, appSecret)` 降低迁移摩擦;`BoaUser` 类型名保留 |
|
||
| CAS 认证 | 本地 dll `AspNetCore.Security.CAS.dll` + Cookie scheme(`AddCasSso()`,[ServiceCollectionExtensions.cs:158-184](file:///d:/SoftProject/yunhai/GrowthHealth/service/Bsch.Web/Code/Extensions/ServiceCollectionExtensions.cs)) | Auth 包需内置等价 CAS handler,摆脱本地 dll |
|
||
| 配置 | 静态 `SiteConfig.AppSetting`(appsettings.json `AppTicket.AppKey=100476`、`UserCenterHost`) | SDK 用 Options 模式,同时提供静态兼容入口 |
|
||
| Redis | `Microsoft.Extensions.Caching.Redis 2.2.0`(旧 IDistributedCache 风格) | SDK 自带 StackExchange.Redis,不依赖消费方缓存栈 |
|
||
| 部署 | Docker `sdk:5.0`/`aspnet:5.0` | SDK 发布无运行时假定 |
|
||
|
||
### 1.2 核心判断(承 v1)
|
||
|
||
* yh-platform 的本质是"HTTP 契约 + 可移植协议 + 客户端封装",服务本体是 UserBase/CasSso/Redis,C# 侧做**同构 NuGet SDK**即可,无需 Java sidecar。
|
||
|
||
* **原则:不要让 .NET 去"依赖 Java",而是让两个 SDK 依赖同一份契约。**
|
||
|
||
* v2 新增判断:**BschOpenApi.dll 已证明".NET 版平台 SDK"这条路的可行性**(GrowthHealth 全面使用其形态);yh-platform-net 不是发明新事物,而是把它升级为:有源码、有版本、有 CI、对齐新 UserBase /oapi 契约(含 BoaRoleInfo 多角色体系)的正规工件。
|
||
|
||
### 1.3 建设目标
|
||
|
||
1. 产出 `yh-platform-net` 独立仓库与 NuGet 包族,能力与 `yh-platform` 同构,且 **net5.0 旧栈可直接消费**;
|
||
2. 产出跨语言契约工件(/oapi OpenAPI spec、签名协议规范、共享测试向量),确立 UserBase 为契约权威源;
|
||
3. 建立双 SDK 版本治理机制(集中版本管理 + 兼容矩阵 + 契约测试),防双端漂移。
|
||
|
||
## 二、目标框架与兼容策略(v2 新增,先拍板)
|
||
|
||
| 决策点 | 结论 | 理由 |
|
||
| -------- | -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
|
||
| SDK 目标框架 | **多目标** **`net5.0; net8.0`**(`netstandard2.0` 不选:需要 `IHttpClientFactory`/`AsyncLocal`/中间件等运行时能力,且 net5 消费方天然兼容) | net5.0 覆盖 GrowthHealth 现状;net8.0(LTS)覆盖新项目与未来升级 |
|
||
| 构建工具链 | .NET SDK **8.0**(可构建 net5.0 目标,targeting pack 经 NuGet 自动还原) | 单一工具链构建双目标 |
|
||
| 测试运行 | 单测跑 **net8.0**;net5.0 仅做编译 + 契约 fixture 反序列化冒烟(net5 runtime 已 EOL,不为测试单独装机) | 平衡成本与置信度 |
|
||
| JSON 序列化 | 模型**纯 POCO 零注解**:net5 消费方 Newtonsoft(大小写不敏感)天然兼容 camelCase;net8 内部用 `JsonSerializerOptions.Web` | 双序列化器兼容且不引入注解漂移 |
|
||
| 依赖策略 | SDK 最小依赖:`Microsoft.Extensions.Http` / `StackExchange.Redis` /(Auth 包)`Microsoft.AspNetCore.Authentication`——net5.0 版本线对齐消费方可用版本 | 避免 SDK 拖动消费方依赖升级 |
|
||
|
||
> 未决事项升级:若决定 GrowthHealth 先升级 .NET 8 再接入,可砍掉 net5.0 目标线(见 9.3 裁决 #1)。
|
||
|
||
## 三、仓库脚手架(从空文件夹开始)
|
||
|
||
### 3.1 目录结构
|
||
|
||
```
|
||
yh-platform-net/ # 独立仓库(与 Java 不混仓)
|
||
├── Directory.Build.props # 共享编译属性(多目标 TFM、LangVersion、确定性构建)
|
||
├── Directory.Packages.props # 集中包版本管理(= Java BOM 等价物)
|
||
├── yh-platform-net.sln
|
||
├── src/
|
||
│ ├── Yh.Platform.Abstractions/ # 模型与信封(对应 yh-utils-core)
|
||
│ │ └── Yh.Platform.Abstractions.csproj
|
||
│ ├── Yh.Platform.OpenApi/ # /oapi 客户端(对应 yh-openapi-client)
|
||
│ ├── Yh.Platform.AppAuth/ # HMAC 签名 + 防重放(对应 yh-app-auth-starter)
|
||
│ └── Yh.Platform.Auth/ # CAS SSO + 用户上下文(对应 yh-auth-starter)
|
||
├── tests/
|
||
│ ├── Yh.Platform.Abstractions.Tests/
|
||
│ ├── Yh.Platform.OpenApi.Tests/ # 含契约 fixture 对拍
|
||
│ ├── Yh.Platform.AppAuth.Tests/ # 含共享测试向量对拍
|
||
│ └── Yh.Platform.Auth.Tests/
|
||
├── samples/
|
||
│ └── Yh.Platform.Sample.Web/ # net8.0 示例应用(平台验收载体)
|
||
├── contract/ # 契约工件(单一事实源派生物)
|
||
│ ├── openapi-oapi.yaml # /oapi OpenAPI 3 spec
|
||
│ ├── sign-protocol.md # 签名协议规范
|
||
│ └── fixtures/
|
||
│ ├── sign-vectors.json # 共享签名测试向量
|
||
│ └── envelope-*.json # 信封解析 fixture(与 Java 对拍)
|
||
├── .gitignore # dotnet 标准模板
|
||
├── .gitea/workflows/ci.yml # 对齐 UserBase/Notice 的 Gitea CI 惯例
|
||
└── README.md
|
||
```
|
||
|
||
### 3.2 初始化命令序列
|
||
|
||
```powershell
|
||
# 前置:已安装 .NET SDK 8.0(dotnet --version >= 8.0)
|
||
mkdir yh-platform-net; cd yh-platform-net
|
||
git init
|
||
dotnet new gitignore
|
||
dotnet new sln -n yh-platform-net
|
||
|
||
# 四包 + 测试 + 示例(Abstractions 为 classlib,其余按依赖逐个建)
|
||
dotnet new classlib -n Yh.Platform.Abstractions -o src/Yh.Platform.Abstractions -f net8.0
|
||
dotnet new classlib -n Yh.Platform.OpenApi -o src/Yh.Platform.OpenApi -f net8.0
|
||
dotnet new classlib -n Yh.Platform.AppAuth -o src/Yh.Platform.AppAuth -f net8.0
|
||
dotnet new classlib -n Yh.Platform.Auth -o src/Yh.Platform.Auth -f net8.0
|
||
dotnet new xunit -n Yh.Platform.OpenApi.Tests -o tests/Yh.Platform.OpenApi.Tests -f net8.0
|
||
dotnet new web -n Yh.Platform.Sample.Web -o samples/Yh.Platform.Sample.Web -f net8.0
|
||
|
||
# 引用关系(镜像 Java 模块依赖图)
|
||
dotnet add src/Yh.Platform.OpenApi/src reference src/Yh.Platform.Abstractions/src # openapi → abstractions
|
||
dotnet add src/Yh.Platform.AppAuth/src reference src/Yh.Platform.Abstractions/src # appauth → abstractions
|
||
dotnet add src/Yh.Platform.Auth/src reference src/Yh.Platform.Abstractions/src # auth → abstractions
|
||
dotnet add src/Yh.Platform.OpenApi.Tests/src reference src/Yh.Platform.OpenApi/src
|
||
# ... 其余同理
|
||
|
||
# 多目标改写:手工把各 csproj 的 <TargetFramework> 改为
|
||
# <TargetFrameworks>net5.0;net8.0</TargetFrameworks>
|
||
# 并在 Directory.Build.props 固化公共属性
|
||
|
||
foreach f in (sln 里所有项目) { dotnet sln add $f }
|
||
dotnet build # 双目标全绿即脚手架完成
|
||
```
|
||
|
||
### 3.3 集中版本管理(BOM 等价物)
|
||
|
||
`Directory.Packages.props`(Central Package Management):
|
||
|
||
```xml
|
||
<Project>
|
||
<PropertyGroup>
|
||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||
<YhPlatformNetVersion>1.0.0</YhPlatformNetVersion>
|
||
</PropertyGroup>
|
||
<ItemGroup>
|
||
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
||
<PackageVersion Include="StackExchange.Redis" Version="2.7.33" />
|
||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||
<PackageVersion Include="xunit" Version="2.9.0" />
|
||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
|
||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||
</ItemGroup>
|
||
</Project>
|
||
```
|
||
|
||
> net5.0 目标线的扩展包版本(如 `Microsoft.Extensions.Http 5.0.x`)通过条件 ItemGroup 区分;`#if NET5_0_OR_GREATER` 符号处理 API 差异(预计极少——`IHttpClientFactory`/`DelegatingHandler` 自 2.1 起稳定)。
|
||
|
||
## 四、四包详细设计(对齐 Java 模块)
|
||
|
||
### 4.1 Yh.Platform.Abstractions(对应 yh-utils-core)
|
||
|
||
| C# 类型 | Java 对齐 | 说明 |
|
||
| -------------------------------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `BoaUser` | `utils.model.BoaUser` | 字段一比一;`CurrentRole` 属性类型 `BoaRoleInfo`(对齐 Java `BoaUser.currentRole`,**替代** GrowthHealth 本地 `Bsch.Entity.Custom.CurrentRole` 散落实现) |
|
||
| `BoaRoleInfo` | `utils.model.BoaRoleInfo` | roleId/roleName/schId/schName/childId/childName,`int?`/`string` |
|
||
| `BoaGroup` / `BoaModule` / `BoaApp` / `BoaAppType` | 同名 | 纯 POCO 零注解(双序列化器兼容,见 2) |
|
||
| `ApiEnvelope` | `ApiResult` | `{code,msg,data}` 信封 + `IsSuccess` 判别(对齐 `AbstractOpenApiClient.unwrap()` 语义) |
|
||
| `DException` | `utils.exception.DException` | 携带 code/msg/data;`code≠0` 时由 OpenApi 包抛出 |
|
||
| `YhErrorCode` | `ErrorCode`/`IErrorCode` | 错误码抽象 |
|
||
|
||
### 4.2 Yh.Platform.OpenApi(对应 yh-openapi-client + starter)
|
||
|
||
| C# 类型 | Java 对齐 | 说明 |
|
||
| -------------------------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `YschBaseOpenApi` | 门面同名 | **静态门面 + 实例构造** **`(baseUrl, appKey, appSecret)`** **双形态**:前者对齐 Java `YschBaseOpenApi` 静态入口;后者复刻 BschOpenApi `BaseOpenApi(host, appKey, appSecret)` 构造习惯,GrowthHealth 迁移时改 using 即可 |
|
||
| `IOpenApiUserClient` / `OpenApiUserClient` | `OpenApiUserClient`/`Default*` | 用户域:byId/byLoginName/byPhone/roles/identity current+switch/password/groupState/transferToSchool 等,端点清单从 Java 接口 javadoc 反推 |
|
||
| `IOpenApiGroupClient` / `OpenApiGroupClient` | 同名 | 组域:tree/byId/list/add/edit/remove |
|
||
| `IOpenApiAppClient` / `OpenApiAppClient` | 同名 | 应用域:byId/list |
|
||
| `OpenApiClientBase` | `AbstractOpenApiClient` | **信封感知解析单点**:成功裸数据 → 反序列化;HTTP 200 + `code≠0` → 抛 `DException`(携带 code/msg/data);`PREFIX="/oapi"` |
|
||
| `OpenApiOptions` | `OpenApiProperties` | `yh:open:` 配置节(base-host/base-path/app-key/app-secret/超时,默认值对齐 Java:connect 1500ms / read 5000ms) |
|
||
| `ServiceCollectionExtensions.AddYhOpenApi()` | `OpenApiAutoConfiguration` | `IHttpClientFactory` 注册 + typed client + `DelegatingHandler` 挂签名 |
|
||
|
||
### 4.3 Yh.Platform.AppAuth(对应 yh-app-auth-starter)
|
||
|
||
| C# 类型 | Java 对齐 | 说明 |
|
||
| ----------------- | ----------------------- | --------------------------------------------------------------------------- |
|
||
| `SignUtil` | `utils.crypto.SignUtil` | `HmacSHA256` 静态方法集,输出格式逐一对齐(见契约 `sign-protocol.md` 裁决) |
|
||
| `AppSignHandler` | `AppSignInterceptor` | `DelegatingHandler`:自动附加 `X-App-Key/X-Timestamp/X-Signature` 头(对齐 Java 头契约) |
|
||
| `SignKeyResolver` | `SignKeyResolver` | 三层:Redis → IMemoryCache → UserBase `/oapi/internal/sign-key/{appKey}` 兜底 |
|
||
| `ReplayGuard` | 防重放 | nonce + 5min 窗口,Redis 键空间与 Java 约定一致 |
|
||
| `AppAuthOptions` | `yh.app-auth.*` | 配置节 |
|
||
|
||
### 4.4 Yh.Platform.Auth(对应 yh-auth-starter)
|
||
|
||
| C# 类型 | Java 对齐 | 说明 |
|
||
| -------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||
| `CasAuthenticationHandler` | `AddCasSso()` 用到的本地 dll | 自研 CAS 远程认证 handler(AuthenticationHandler 基类),替换 `AspNetCore.Security.CAS.dll`:CasServerUrlBase、service ticket 校验 `/serviceValidate` |
|
||
| `BoaUserContext` | `UserContextHolder` | `AsyncLocal<BoaUser>`(替代 Java ThreadLocal 语义) |
|
||
| `IUserDetailsProvider` | `UserDetailsProvider` SPI | 默认实现走 `IOpenApiUserClient` 换取 `BoaUser` |
|
||
| `AddYhAuth()` | `YhAuthAutoConfiguration` | Cookie + CAS 认证注册 + 用户上下文中间件 |
|
||
|
||
**聚合入口**:`AddYhPlatform()` 一次带齐(等价 Java `yh-spring-boot-starter`),也可按包拆引。
|
||
|
||
## 五、契约工件(MVP 前置门禁,承 v1 第六章)
|
||
|
||
1. `contract/sign-protocol.md`:权威签名方案(数据级 `data|ts` 小写 hex vs 请求级 `METHOD\nurl\nts` Base64——需裁决)、验签大小写、时间戳毫秒、5min 窗口;
|
||
2. `contract/openapi-oapi.yaml`:UserBase `controller.oapi` 四域全端点(springdoc 导出 + 人工补 `oneOf` 错误信封),排除 `/oapi/internal/**`;
|
||
3. `contract/fixtures/sign-vectors.json`:≥10 组签名向量(**由 Java** **`SignUtil`** **实际运行产出基线**,含空 body/超长参数/时间戳边界);
|
||
4. `contract/fixtures/envelope-*.json`:信封样例(成功裸数据/失败 code=4/5),双端解析对拍。
|
||
|
||
## 六、构建、CI 与发布
|
||
|
||
| 环节 | 方案 |
|
||
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||
| 本地构建 | `dotnet build`(双目标);`dotnet test`(net8.0 全量) |
|
||
| 契约对拍 | 测试内加载 `contract/fixtures/*.json`(`CopyToOutputDirectory`),断言 C# 输出与 fixture 逐字节一致 |
|
||
| CI(Gitea,对齐 UserBase/Notice 惯例) | `dotnet build -c Release` → `dotnet test` → `dotnet pack -c Release -o artifacts`;Java 侧 CI 增加一步"重算签名向量 fixture"防漂移 |
|
||
| 发布 | `dotnet nuget push` 到 Nexus 3 NuGet 仓库(`nexus.yunhaixingtong.com`),与 Maven 同私服 |
|
||
| 版本策略 | SemVer;包版本统一随 `YhPlatformNetVersion` 走(对齐 BOM 集中钉版本);兼容矩阵登记 `yh-platform ↔ yh-platform-net ↔ /oapi 契约` |
|
||
|
||
## 七、分阶段实施步骤(每步有完成标志)
|
||
|
||
| 阶段 | 内容 | 完成标志 |
|
||
| ------------------- | -------------------------------------------------------------------------------- | ------------------------------------------- |
|
||
| **P0 环境与裁决** | 装 .NET SDK 8;裁决 9.3 的 #1(net5.0 目标线去留)与 #2(签名方案);Java 侧跑出 `sign-vectors.json` 基线 | `dotnet --version ≥ 8`;裁决记录进本文档 9.3 |
|
||
| **P1 契约提纯** | 产出第五节四件契约工件 | 自检清单 A 全勾 |
|
||
| **P2 脚手架** | 第三节命令序列跑通,空壳四包 + 测试 + sample 编译绿 | `dotnet build` 双目标零 warning(新增代码零 warning) |
|
||
| **P3 Abstractions** | 全模型 + 信封 + 异常;用 `envelope-*.json` 做反序列化对拍测试 | Abstractions.Tests 绿 |
|
||
| **P4 AppAuth** | `SignUtil` + `AppSignHandler` + `SignKeyResolver`;`sign-vectors.json` 100% 对拍 | AppAuth.Tests 绿,含向量逐条断言 |
|
||
| **P5 OpenApi** | 三域 client + 门面双形态 + `AddYhOpenApi()`;对接 UserBase 测试环境端到端 | sample 应用成功取到 `BoaUser`(含 currentRole)与角色列表 |
|
||
| **P6 Auth** | CAS handler + `BoaUserContext` + `AddYhAuth()` | sample 应用 CAS 登录 → 上下文注入 `BoaUser` |
|
||
| **P7 打包发布** | `dotnet pack` + Nexus push + CI 固化 + 兼容矩阵登记 | Nexus 可 `dotnet add package` 拉到 |
|
||
| **P8 消费方就绪文档** | 《.NET 消费方接入指南》(appKey 注册、配置样例、从 BschOpenApi 迁移对照表) | 指南成文并评审 |
|
||
|
||
## 八、完成后自检清单(Self-Check)
|
||
|
||
> 用法:**SDK 建完(P0\~P6 收尾)后,一次性照单跑一遍**。全部打勾即合格;任何一项不勾,修完再勾,不留尾巴。
|
||
|
||
### 8.1 工程自检(有没有问题)
|
||
|
||
* [ ] `git clone` 空机器 + `dotnet build` 一次通过(含 net5.0;net8.0 双目标,前提是裁决保留了 net5.0 线)
|
||
|
||
* [ ] `dotnet test` 全绿(含契约 fixture 对拍)
|
||
|
||
* [ ] 无 `#if` 之外的 TFM 差异残留;无 TODO/FIXME 遗留
|
||
|
||
* [ ] 包引用关系正确:OpenApi/AppAuth/Auth → Abstractions,无反向依赖
|
||
|
||
* [ ] `Directory.Packages.props` 全部包版本集中管理,csproj 内无散落版本号
|
||
|
||
* [ ] NuGet 包元数据完整(作者、描述、仓库地址、license)
|
||
|
||
* [ ] `dotnet pack` 产物可被空控制台项目 `dotnet add package` 正常引用还原
|
||
|
||
### 8.2 契约自检(和 Java 侧对不对得上)
|
||
|
||
* [ ] `sign-vectors.json` 全部向量 C# 输出逐字节一致(100% 对拍)
|
||
|
||
* [ ] `envelope-*.json` 全部样例:成功裸数据反序列化正确;失败信封抛 `DException` 且携带 code/msg/data
|
||
|
||
* [ ] 模型字段与 Java `BoaUser/BoaRoleInfo` 等一比一(含 `BoaUser.CurrentRole: BoaRoleInfo` 嵌套)
|
||
|
||
* [ ] 同一份 fixture JSON 用 Newtonsoft(net5 形态)与 STJ(net8 形态)都能正确反序列化
|
||
|
||
* [ ] `X-App-Key/X-Timestamp/X-Signature` 头名与 Java 逐字一致
|
||
|
||
### 8.3 运行自检(跑起来对不对)
|
||
|
||
* [ ] sample 应用签名调 /oapi 成功取到 `BoaUser`(含 currentRole)
|
||
|
||
* [ ] CAS 登录 → `BoaUserContext` 注入正确;请求结束上下文清空
|
||
|
||
* [ ] 超时默认 connect 1500ms / read 5000ms 生效
|
||
|
||
* [ ] 防重放:同一签名 5 分钟内二次请求被拒
|
||
|
||
## 九、审计核对(功能逻辑逐项验证)
|
||
|
||
> 用法:**自检全勾后**,按功能逐项核对"输入 → 预期行为",确认逻辑与 Java 侧等价。核对方式:跑 sample/单测实际验证,不靠读代码。每项给结论:通过 / 不通过(说明原因)。
|
||
|
||
### 9.1 核对表
|
||
|
||
| # | 功能 | 核对内容(输入 → 预期) | 方式 | 结果 |
|
||
| -- | --------- | ----------------------------------------------------------------------------- | -------------- | ------------------------------------------------- |
|
||
| 1 | HMAC 签名生成 | 固定 `(data, ts, secret)` → 输出与 Java `SignUtil` 完全一致 | 测试向量对拍 | ✅ 通过(向量 100% 对拍 + 独立实现抽检 6/6) |
|
||
| 2 | 签名请求头附加 | 任意经 SDK 发出的 HTTP 请求 → 自动带三头,值与手算一致 | 抓包/单测 | ✅ 通过(P1 修复后回归:handler 对 fullUrl 签名与 Java 基线逐字节一致) |
|
||
| 3 | 信封成功分支 | /oapi 返回裸数据/裸数组 → 正确反序列化为模型;数组含单元素与多元素 | fixture 单测 | ✅ 通过 |
|
||
| 4 | 信封失败分支 | HTTP 200 + `{code:4/5, msg, data}` → 抛 `DException`,code/msg/data 原样携带 | fixture 单测 | ✅ 通过(data 载体更名 `EnvelopeData`,P2 修复) |
|
||
| 5 | 业务异常等价性 | Java 侧 `catch(DException)` 的代码,C# 侧 `catch(DException)` 能拿到同样的 code/msg | 对照 | ✅ 通过 |
|
||
| 6 | 用户查询三入口 | byId / byLoginName / byPhone → 同一测试用户返回字段一致的 `BoaUser` | sample 实调 | ⏳ 待环境(UserBase 测试环境 + appKey 注册) |
|
||
| 7 | 多角色读取 | `getUserRoleList` → 角色列表字段完整(roleId/roleName/schId/schName/childId/childName) | sample 实调 | ⏳ 待环境 |
|
||
| 8 | 当前身份 | `getCurrentIdentity` → 与 UserBase `sys_user_current_role` 状态一致;未切换时回退主身份 | sample 实调 | ⏳ 待环境 |
|
||
| 9 | 身份切换(若需) | `switchIdentity` → 白名单 appKey 通过、非白名单拒绝、三元组非法拒绝 | sample 实调 | ⏳ 待环境 |
|
||
| 10 | 防重放 | 同 timestamp+nonce 二次请求 → 拒绝;5 分钟前时间戳 → 拒绝 | 实调 | ✅ 通过(逻辑单测:MemoryReplayGuard + 双向偏差窗口) |
|
||
| 11 | 密钥解析链 | Redis 有值 → 不请求 UserBase;Redis 空 → 兜底取到并回填缓存 | 断点/日志 | ✅ 通过(代码级核对,兜底实调待环境) |
|
||
| 12 | CAS 登录 | sample 走完 CAS 重定向 + ticket 校验 → 登录态建立、`BoaUser` 注入 | 浏览器实走 | ⏳ 待环境(CAS server 联通) |
|
||
| 13 | 用户上下文生命周期 | 请求内任何层可取 `BoaUserContext.Current`;请求结束不串到下一请求(并发压一下) | 单测/实调 | ✅ 通过(单测) |
|
||
| 14 | 错误传播 | UserBase 不可达/超时 → 抛超时类异常而非挂死;HTTP 非 2xx → 异常带状态码 | 实调(断 UserBase) | ✅ 通过(FakeHandler 模拟 500/异常路径) |
|
||
| 15 | 双形态门面 | `YschBaseOpenApi` 静态门面与 `new (baseUrl, appKey, appSecret)` 实例,行为一致 | 单测 | ✅ 通过 |
|
||
|
||
### 9.2 审计结论记录
|
||
|
||
> 全部核对完成后填写;"不通过"项须附原因与修复决定。
|
||
|
||
| 日期 | 核对人 | 通过 / 不通过项 | 遗留问题与决定 |
|
||
| ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------- |
|
||
| 2026-08-28 | 架构验收(工具辅助) | **通过 11 项**(#1 签名向量、#3/#4 信封双分支、#5 异常等价、#10 防重放逻辑、#11 密钥链代码、#14 错误传播、#15 双形态门面等);**不通过 1 项(#2)**;待环境 3 项(#6\~#9、#12 实调类) | 见下方 2026-08-28 验收详情 |
|
||
|
||
**2026-08-28 验收详情(BasePlatformDotnet,测试 67/67 绿)**:
|
||
|
||
* **不通过 #2(P1 阻断)**:`AppSignHandler` 签名 target 未剥离 basePath。实证:Java 客户端 `AppSignInterceptor` 签名前 `stripBasePath`(配置 base-path=/userbase),服务端 `AppAuthFilter.buildSignedTarget` 剥 `request.getContextPath()`——两端验签口径为 `/oapi/**`;而 C# `AppSignHandler` 用 `uri.AbsolutePath`(含 `/userbase` 前缀)。UserBase 挂 context-path `/userbase` 时 C# 出站签名必被服务端拒绝。**根因连带**:`sign-vectors.json` 请求级向量的 `canonicalTarget` 含 `/userbase` 前缀(生成时 Java 侧 basePath 未配置),向量与 C# 实现"自洽"但均偏离服务端口径。**修复决定**:AppSignHandler 增加 basePath 剥离参数;同步修正请求级向量(canonicalTarget 去 `/userbase` 前缀)并在 `sign-protocol.md` 写明剥离规则;补一个"剥离后与 Java 拦截器同 URL 签名一致"的对拍测试。
|
||
|
||
* **P2**:`DException.Data` 遮蔽 `Exception.Data`(编译警告 CS0114,违反"零 warning"自检项)。`Exception.Data` 是 IDictionary 语义,被 `object?` 遮蔽后框架代码按基类契约访问会异常。决定:改名 `EnvelopeData`(或至少显式 `new` + 文档化),消除警告。
|
||
|
||
* **P3 观察项**:path 解码差异——Java 拦截器 `URLDecoder`(`+`→空格)vs C# `AbsolutePath`(保留 `+`);pack/Nexus push 未实际执行(P7 范畴);8.3 运行自检整组待 UserBase 测试环境(appKey 注册、网络可达)后补验 #6\~#9、#12。
|
||
|
||
* **独立验证**:签名向量经第三方实现(独立 HMACSHA256 脚本)抽样 6/6 MATCH,确认拼接顺序/编码/大小写跨实现一致。
|
||
|
||
**2026-08-28 修复执行记录(P1/P2 已闭环,回归 83/83 绿、双目标构建零 warning)**:
|
||
|
||
* **P1 basePath 剥离**:`SignCanonical` 新增 `StripBasePath`(逐行对齐 Java `stripBasePath`,含尾斜杠归一化与前缀不完整匹配不剥离边界)与 `BuildTarget(Uri, basePath)` 重载;`AppSignHandler` 增加 basePath 参数(`NormalizeBasePath` 归一化,空=挂根不剥离),DI 扩展与 `BaseOpenApi` 均已接线传入 `OpenApiOptions.BasePath`;时钟源统一走 `SignUtil.NowMillis`(可注入,测试可固定 ts 与基线逐字节对拍)。
|
||
|
||
* **P1 向量修正**:`sign-vectors.json` 六个请求级用例 canonicalTarget 去 `/userbase` 前缀并以独立 HMAC 实现重算期望值;新增 `fullUrl`/`basePath` 字段复现剥离前 URL;`requestLevel.note` 写明剥离口径。
|
||
|
||
* **P1 对拍测试**:新增 `AppSignHandlerTests`(6 个向量驱动的 handler 对 fullUrl 签名与 Java 基线逐字节一致、剥离+query 排序、未配置 basePath 不剥离、7 组 StripBasePath 边界、BuildTarget 双形态)。
|
||
|
||
* **P2**:`DException.Data` → `EnvelopeData`(消除 CS0114 对 `Exception.Data` 的遮蔽),`sign-protocol.md` 2.2 节补 basePath 剥离规则。
|
||
|
||
* **协议文档**:`sign-protocol.md` 2.2 新增"basePath 剥离(客户端与服务端共同口径)"条款。
|
||
|
||
* 遗留:P3 观察项(path `+` 解码差异)与待环境项(#6\~#9、#12、Nexus push)不变。
|
||
|
||
**2026-08-28 双端同步闭环(契约快照对拍机制建成)**:
|
||
|
||
* **流程补课**:UserBase 加 `/oapi` 文档分组(`SwaggerConfig.oapiGroup`:pathsToMatch /oapi/\*\* + pathsToExclude /oapi/internal/\*\*;prod 用 `SPRINGDOC_API_DOCS_ENABLED=false` 关闭)→ 真实导出 `contract/openapi-oapi.exported.json` 入库作为权威基线 → C# 侧 `ContractSnapshotTests`(15 个用例)对拍,双端同步机制正式生效。此前"手写 yaml 自称权威源"的流程偏差就此纠正。
|
||
|
||
* **对拍机制**:①端点对拍——SDK 路由表 ⊆ 快照(严格失败,UserBase 删改端点立刻红);快照多出的端点须显式登记白名单否则失败(防无声失联);②模型对拍——13 个 C# POCO 属性集(camelCase)⇔ 快照 schema properties 键集双向差额为零(反射驱动,UserBase 改字段立刻红)。
|
||
|
||
* **首战战果(快照暴露 4 个 SDK 真漂移,全部已修)**:① `SysUserRequest.Roles` → `RoleIds`(序列化键 `roles` 服务端收不到,会实际跑坏);② `SysUserRequest` 补 `SchoolId`;③ `SysGroupRequest` 补 `Id`;④ 新增 `GetAvailableIdentitiesAsync`(/oapi/identity/available,多角色配套端点此前缺失)。另修正手写 yaml 4 处文档偏差(byId id / appId 类型、clientType required、roles→roleIds+schoolId)。
|
||
|
||
* **基线更新流程**(写入测试 xml doc):UserBase 契约变更 → 起服务 `GET /v3/api-docs/oapi` 覆盖 exported.json → 跑测试,红了即漂移,同步修 SDK 或登记白名单。
|
||
|
||
* 回归:测试 98/98 绿(+15)。
|
||
|
||
### 9.3 方案合规核对(建设前依据,承 v2 方案审计)
|
||
|
||
| 审计项 | 结论 | 依据 |
|
||
| ------------------------ | -- | ----------------------------------------------------- |
|
||
| 是否避免为 .NET 引入额外常驻运行单元 | 通过 | 无 sidecar、无网关耦合 |
|
||
| 是否与既有 yh-platform 架构风格一致 | 通过 | 四包 1:1 镜像;"core 纯契约 + 装配扩展"分层同源 |
|
||
| 是否与既有 BOM/私服治理一致 | 通过 | CPM = BOM 等价物;Nexus 3 同私服承载 NuGet |
|
||
| 是否复用既有基础设施 | 通过 | springdoc 已在 UserBase;Redis/CasSso 复用;模型编译期共享(Java 侧) |
|
||
| 影响面是否最小化 | 通过 | yh-platform 零改动;UserBase 仅配置级(spec 分组) |
|
||
| **兼容旧栈**(v2 新增) | 通过 | 多目标 net5.0;Newtonsoft 兼容模型;`BaseOpenApi` 构造形态复刻 |
|
||
| **可从空文件夹直接落地**(v2 新增) | 通过 | 第三节命令序列 + 第七节分阶段步骤,每步有完成标志 |
|
||
| 淘汰路径 | 通过 | 阶段化交付可独立回退;最坏降级方案 C(sidecar)过渡 |
|
||
|
||
### 9.4 消费方代码实证(2026-08-28,建设依据)
|
||
|
||
| 核查对象 | 核查点 | 结果 |
|
||
| ----------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
|
||
| `GrowthHealth/service/Bsch.Web/Bsch.Web.csproj` | 目标框架/依赖 | 实证:net5.0;Newtonsoft 5.0.2 适配器;本地 dll 引用 BschOpenApi/AspNetCore.Security.CAS/log4net |
|
||
| `Bsch.Common.csproj` | 基础设施栈 | 实证:Redis 缓存 2.2.0(旧式)、Newtonsoft 13.0.1、Serilog 4.1.0 |
|
||
| `Bsch.Repository/Repository/UserInfo.cs` | 旧 SDK 用法 | 实证:`new BaseOpenApi(UserCenterHost, appKey, appSecret)`;`BoaUser` 来自 `BschBaseOpenApi` 命名空间;CurrentRole 为本地实体+请求头拼装 |
|
||
| `ControllerAbstract.cs` | 控制器基类消费模式 | 实证:每个控制器 new 一个 BaseOpenApi(无 DI/无连接复用)——新 SDK 需以 IHttpClientFactory 修正此反模式 |
|
||
| `ServiceCollectionExtensions.cs`(AddCasSso) | CAS 认证形态 | 实证:Cookie + `AddCAS()`(本地 dll);CasServerUrlBase 取自配置 |
|
||
| `appsettings.json` | 配置契约 | 实证:`AppTicket.AppKey=100476`、UserCenterHost/SsoHost/SchClassHost——新 SDK Options 节需给出从旧键迁移的映射 |
|
||
| `Dockerfile` | 部署形态 | 实证:sdk:5.0/aspnet:5.0,aspnet:5.0 基础镜像已 EOL(升级建议列入消费方指南) |
|
||
| Java 侧核查(承 v1) | SignUtil 双规则/信封/springdoc/模型共享 | 结论不变:内容合格、形式待提纯;详见 v1 审计表 |
|
||
|
||
### 9.5 未决事项(待裁决)
|
||
|
||
| # | 事项 | 裁决人 | 截止 | v2 备注 |
|
||
| - | ------------------------------------------------------------ | ------------------- | ---- | -------------------------------------------------------- |
|
||
| 1 | 保留 `net5.0` 目标线,还是要求消费方先升 .NET 8 | 平台负责人 | P0 | 保留=SDK 复杂度+双目标;砍掉=GrowthHealth 必须先升级(含 EF Core/基础镜像连带升级) |
|
||
| 2 | 权威签名方案:数据级(`data\|ts` 小写 hex)还是请求级(`METHOD\nurl\nts` Base64) | 平台负责人 | P1 前 | 旧 BschOpenApi 实际用哪套可作为裁决输入(反编译或抓包确认) |
|
||
| 3 | 请求级验签大小写敏感是否设计意图 | yh-platform 维护者 | P1 前 | — |
|
||
| 4 | C# 模型手写 vs NSwag/Kiota 生成 | yh-platform-net 负责人 | P3 前 | v2 倾向手写(模型量小、需 Newtonsoft 零注解兼容) |
|
||
| 5 | 双签名规则长期收敛 | 平台负责人 | 长期 | 不阻塞 |
|
||
|
||
### 9.6 变更记录
|
||
|
||
| 日期 | 版本 | 说明 |
|
||
| ---------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| 2026-08-28 | v1.0 | 初版:平台工件建设定位,方案选型、架构、影响面实证、自检清单、审计 |
|
||
| 2026-08-28 | v2.0 | 基于消费方实证(GrowthHealth net5.0 + BschOpenApi.dll 考古):新增目标框架兼容策略(net5.0;net8.0 多目标)、仓库脚手架命令序列(Directory.Build.props/CPM)、四包详细设计(含 `YschBaseOpenApi` 双形态复刻 BaseOpenApi 构造习惯)、分阶段实施步骤、自检清单 B 工程门禁、审计新增兼容旧栈与空文件夹可落地两项 |
|
||
| 2026-08-28 | v3.0 | 自检/审计简化落地:多阶段门禁改为"完成后一次性自检"(工程/契约/运行三组)+ "功能逻辑审计核对表"(15 项输入→预期核对,含审计结论记录表);原方案审计/代码实证降级为建设依据附录 |
|
||
|