43 KiB
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) | 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、SchClassService.cs:22) |
yh-platform-net = BschOpenApi 的重生。需提供同构构造函数形态 (host, appKey, appSecret) 降低迁移摩擦;BoaUser 类型名保留 |
| CAS 认证 | 本地 dll AspNetCore.Security.CAS.dll + Cookie scheme(AddCasSso(),ServiceCollectionExtensions.cs:158-184) |
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 建设目标
- 产出
yh-platform-net独立仓库与 NuGet 包族,能力与yh-platform同构,且 net5.0 旧栈可直接消费; - 产出跨语言契约工件(/oapi OpenAPI spec、签名协议规范、共享测试向量),确立 UserBase 为契约权威源;
- 建立双 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 初始化命令序列
# 前置:已安装 .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):
<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 第六章)
contract/sign-protocol.md:权威签名方案(数据级data|ts小写 hex vs 请求级METHOD\nurl\ntsBase64——需裁决)、验签大小写、时间戳毫秒、5min 窗口;contract/openapi-oapi.yaml:UserBasecontroller.oapi四域全端点(springdoc 导出 + 人工补oneOf错误信封),排除/oapi/internal/**;contract/fixtures/sign-vectors.json:≥10 组签名向量(由 JavaSignUtil实际运行产出基线,含空 body/超长参数/时间戳边界);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(逐行对齐 JavastripBasePath,含尾斜杠归一化与前缀不完整匹配不剥离边界)与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.md2.2 节补 basePath 剥离规则。 -
协议文档:
sign-protocol.md2.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 项输入→预期核对,含审计结论记录表);原方案审计/代码实证降级为建设依据附录 |