bag
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# 2026-09-01
|
||||
|
||||
## 修复:CS0121 Deserialize 重载二义性(Yh.Platform.AppAuth)
|
||||
|
||||
- 文件:`src/Yh.Platform.AppAuth/SignKeyResolver.cs` 第 49~52 行。
|
||||
- 现象:`JsonSerializer.Deserialize<T>(json!, JsonOpts)` 报 CS0121,在
|
||||
`Deserialize<TValue>(ReadOnlySpan<byte>, JsonSerializerOptions?)` 与
|
||||
`Deserialize<TValue>(string, JsonSerializerOptions?)` 之间二义。
|
||||
- 根因:`json` 是 StackExchange.Redis 的 `RedisValue`,它同时定义了到 `string` 和 `byte[]` 的隐式转换运算符,
|
||||
`byte[]` 又可隐式转 `ReadOnlySpan<byte>`,两条用户定义转换路径都可行 → 编译器无法取舍。
|
||||
- 修复:先显式取 string,`var json = (string?)await redis.StringGetAsync(...)`,再用
|
||||
`!string.IsNullOrEmpty(json)` 判断(原 `json.HasValue` 语义等价,RedisValue.Null 转 string 得 null)。
|
||||
- 验证:`dotnet build yh-platform-net.sln` 全解决方案 0 警告 0 错误。
|
||||
|
||||
### 通用经验
|
||||
凡是把 `RedisValue`(或其他带多个隐式转换运算符的类型)直接传给重载方法,都可能触发 CS0121。
|
||||
统一约定:从 Redis 取出的值先显式转换成目标 CLR 类型(`(string?)` / `(byte[]?)` / `(long?)`),再传给下游 API。
|
||||
|
||||
## 新增项目:Yh.SchClass.OpenApi(类库,net5.0;net8.0)
|
||||
|
||||
- 路径:`src/Yh.SchClass.OpenApi/`,目录下原有空文件夹(sln 未引用),本次补齐。
|
||||
- 新增文件:
|
||||
- `Yh.SchClass.OpenApi.csproj`:继承 `Directory.Build.props` 默认 `TargetFrameworks=net5.0;net8.0`,仅设 `PackageId`/`Description`/`Summary`,无额外 PackageReference(最小起步)。
|
||||
- `SchClassOpenApiEntry.cs`:根命名空间 `Yh.SchClass.OpenApi` 的占位静态类 `SchClassOpenApi`,避免空库 NETSDK1082 警告。
|
||||
- sln 注册:GUID `{2F8CD5DF-3CE6-405C-A15A-CC0CC04202C4}`,已加入 Project 声明、ProjectConfigurationPlatforms(6 行 Debug/Release × AnyCPU/x64/x86)与 NestedProjects(归入 samples 方案文件夹 `{5D20AA90...}`)。
|
||||
- 验证:`dotnet build yh-platform-net.sln` 全方案 0 警告 0 错误,新建项目双目标均通过。
|
||||
- 约定:新增 src 类库时默认沿用中央包管理(Directory.Packages.props),csproj 内不写版本号;项目需手动在 sln 注册到 `samples` 文件夹下。
|
||||
|
||||
## 新功能:Yh.SchClass.OpenApi 实现 Java OA 端点客户端(69 个方法)
|
||||
|
||||
- 需求来源:`E:\YHXT\java\bschclassmanage\...\controller\oapi\oa` 下 12 个 Java 控制器,逐一映射为 .NET 客户端方法。
|
||||
- Java 侧关键事实:
|
||||
- 前缀 `Config.OAPI = "/oapi"`(config/Config.java:16);控制器各自追加 `/DbClass/`、`/VSchClass/`、`/Schoolen/`、`/UserInfo/`、`/SchInfo/`、`/OATea/`、`/OAStu/`、`/TeachInfo/`、`/SubInfo/`、`/Common/FamCom/`。
|
||||
- 端点返回**裸 JSON**(对象/数组/字符串数组),无统一信封。
|
||||
- 用 HTTP 404 / 204 表达"查无数据",HTTP 400 表达参数缺失 —— 客户端需把 404/204 归一为 null/空列表,不能当异常抛。
|
||||
- 参数名大小写混用且敏感:`SchSerID` / `schserid` / `Schooling` / `schId` / `Group` / `ClassID` / `SchIDArr` / `userid` 等,必须逐字照搬。
|
||||
- `SchGroupController` 是空壳(0 端点),不生成客户端;`OaCompatSupport` 是行映射工具类,非端点。
|
||||
- 设计决策:
|
||||
- **零项目依赖**:自建 `SchClassClientBase`,不引用 `Yh.Platform.OpenApi`(避免拖入 AppAuth 依赖链),可独立打包。
|
||||
- `Map<String,Object>` 兼容行用 `Dictionary<string, JsonElement>`,通过 `global using SchClassRow = ...` 别名收敛(net5.0 可用,不用 net6+ 的 JsonNode)。
|
||||
- VO 用 `[JsonPropertyName]` 锚定 **Java 字段原名**(如 `IDCard`、`SchSerID`),配合 `JsonSerializerDefaults.Web` 的大小写不敏感匹配,可同时兼容 Jackson 从 getter 推导出的 `idcard`、`schSerID` 等变体。
|
||||
- POST 的 `@RequestBody String` 端点按 `text/plain` 提交原始逗号分隔串(非 JSON 包裹),否则 Spring 侧会带到引号。
|
||||
- **信封识别加严**:只有同时出现数字 `code` 且(有 `data` 或有字符串 `msg`)才判为信封。否则学制等级行 `{Id,Name,OwnerId,Levell,code,sx}` 中的 `code=gradeId` 会被误判成错误响应而抛异常。
|
||||
- 交付文件(`src/Yh.SchClass.OpenApi/`):
|
||||
- 基础设施:`GlobalUsings.cs`、`SchClassClientBase.cs`、`SchClassOpenApiOptions.cs`、`SchClassOpenApiException.cs`
|
||||
- DTO:`SchInfoVo.cs`、`UserInfoVo.cs`、`TeaInfoVo.cs`
|
||||
- 客户端 10 个:DbClass(1) / VClass(17) / Schooling(5) / UserInfo(2) / SchInfo(7) / TeaInfo(4) / StuInfo(19) / TeachInfo(8) / SubInfo(4) / FamilyCompat(2)
|
||||
- 门面:`SchClassOpenApi.cs`(含 `SchClassOpenApiFactory` 轻量工厂)
|
||||
- 端点数与方法数 1:1 对齐(合计 69),已用 grep 双向核对。
|
||||
- 踩坑:`Unwrap` 里先把 `out var codeEl` 的 `&&` 链赋值给 bool 变量再使用,编译器不做相关性分析 → CS0165「使用了未赋值的局部变量」。改为直接 `if` 分支判定即可。
|
||||
- 验证:`dotnet build yh-platform-net.sln` 全方案 **0 警告 0 错误**,net5.0 与 net8.0 双目标均通过。
|
||||
|
||||
## 新增项目:Yh.SchClass.OpenApi.Tests(xunit,net8.0)+ 联调调试项
|
||||
|
||||
- 路径:`tests/Yh.SchClass.OpenApi.Tests/`,GUID `{E70492B2-109A-4E6F-AF91-443AC2956E3A}`,sln 三处已注册(Project / ProjectConfigurationPlatforms / NestedProjects 归入 `samples` 方案文件夹 `{5D20AA90...}`)。
|
||||
- 结构对齐 `Yh.Platform.OpenApi.Tests`:`net8.0` + `IsPackable=false` + xunit 三件套(版本号走 CPM,csproj 内不写版本)。
|
||||
- 交付文件:`GlobalUsings.cs`、`FakeHandler.cs`(同构于平台测试的捕获/回放 handler,额外记录 Content-Type)、
|
||||
`SchClassTestHost.cs`(共享 Options、`Verify`/`VerifyBody` 契约断言)、
|
||||
`SchClassOpenApiOptionsTests.cs`、`SchClassResponseParsingTests.cs`(裸数据/信封/404/204/400/500/非法JSON/学制行 code 不误判)、
|
||||
`SchClassVClassClientTests.cs`(17 端点)、`SchClassStuInfoClientTests.cs`(19 端点)、
|
||||
`SchClassSchInfoClientTests.cs`(7)、`SchClassTeaInfoClientTests.cs`(4)、`SchClassUserInfoClientTests.cs`(2)、
|
||||
`SchClassOtherClientsTests.cs`(Schooling/SubInfo/TeachInfo/DbClass/FamCom 共 20)、
|
||||
`SchClassOpenApiFacadeTests.cs`(门面装配与工厂)。
|
||||
- **调试项**:`SchClassLiveDebug.cs` + `SchClassLiveDebugTests.cs`。设 `YH_SCHCLASS_DEBUG_HOST`(可选 `_PATH`/`_SCH`/`_CLASS`/`_TEA`)后对真实后端发请求并逐端点打印条数与异常;未设则自动空跑通过,CI 安全。一键入口是 `联调_一键跑通全部端点`(20 个端点汇总表)。用法示例见文件头注释。
|
||||
- 顺带修复:`src/Yh.SchClass.OpenApi/Model/*.cs` 的 namespace 仍是 `Yh.SchClass.OpenApi`,而 3 个 client 写的是 `using Yh.SchClass.OpenApi.Model;` → 6 个 CS0234。已把三个 VO 的 namespace 改为 `Yh.SchClass.OpenApi.Model`(与 `Model/` 目录一致)。
|
||||
- 测试踩坑:`TeaInfoVo.UserID` 是**用户中心 ID**,与查询参数 `uid`(教师 teaId)不是同一个值,写断言时易混。
|
||||
- 验证:`dotnet test yh-platform-net.sln` 全绿 —— Abstractions 8 / SchClass 63 / OpenApi 37 / Auth 19 / AppAuth 42,共 169。
|
||||
|
||||
## 联调地址改为真实环境 + 首次真实联调通过
|
||||
|
||||
- **教务开放接口真实地址**:`http://base.yunhaixingtong.com/bschclass`
|
||||
- 拆解:`BaseHost = http://base.yunhaixingtong.com`,`BasePath = /bschclass`(Java 工程的 context-path)。
|
||||
- 完整示例:`http://base.yunhaixingtong.com/bschclass/oapi/DbClass/GetSchteaList?Group=3`
|
||||
- 改动:
|
||||
- `SchClassLiveDebug.cs`:新增内置默认 `DefaultHost`/`DefaultPath`,**默认即联调环境,开箱即用**;
|
||||
新增 `Enabled` 开关(默认开)与 `YH_SCHCLASS_DEBUG_OFF=1` 逃生开关(CI 用,置 1 则全部空跑通过);
|
||||
新增 `GroupVar`(`YH_SCHCLASS_DEBUG_GROUP`,缺省 3)。
|
||||
- `SchClassLiveDebugTests.cs`:缺省 `classId`/`teaId` 由捏造值(1001/77)改为**从真实数据推导** ——
|
||||
classId 取 `VClass.GetVClassList` 首条的 `ClassID`,teaId 取 `DbClass.GetSchteaList` 首条的 `UserTeaSerID`。
|
||||
这一改让 `GetClaStulist` 0→28 条、`TeachInfo` 两个端点 0→2/4 条,证明此前是参数不对而非接口空。
|
||||
- `SchClassOpenApiOptions.cs`:XML 注释示例改为真实地址。
|
||||
- **真实联调结果(20/20 通过)**:SchoolInfo 5 / VSchClass 29·65 / UserInfo 51 / OAStu 89·64·28 /
|
||||
SubInfo 4·18 / DbClass 19 / TeachInfo 2·4。
|
||||
- 仍为 0 条的端点(**非客户端问题,已核实**):
|
||||
- `OATea.GetSchTeainfo` / `GetArrTeainfo` —— 后端返回 `[null,null,...]`(19 个 null),
|
||||
`ResolveListAsync` 会跳过数组中的 null 元素 → 0 条。属后端 VO 转换失败的数据问题。
|
||||
- `FamCom.*` 两个 —— Java 源码里就是 `return new ArrayList<>()`,恒空。
|
||||
- 修复:`SchClassOpenApiFacadeTests.工厂_按配置创建HttpClient` 断言里的 path 被误写成
|
||||
`/base.yunhaixingtong.com`(应为 `/bschclass`),之前因 host 也是该串而偶然通过 → 改为 `/bschclass`。
|
||||
- 修复:`SchClassLiveDebugTests.cs:124` CS8604(nullable 警告)—— `teaId` 推导后类型仍是 `int?`,
|
||||
改用 `teaId.Value.ToString()`。
|
||||
- 踩坑:`curl -o /tmp/xxx` 在 Git Bash 下 exit 23 写文件失败(0B),改用工作区相对路径输出。
|
||||
- 验证:`dotnet build yh-platform-net.sln` 0 警告 0 错误;`dotnet test` 169 全通过。
|
||||
|
||||
## 配置文件读取:yh:open 共享节 + yh:schclass:open 独立节
|
||||
|
||||
- 真实配置结构(`tests/Yh.SchClass.OpenApi.Tests/appsettings.json` 的 `yh:open` 节,UserBase 与教务共享):
|
||||
```json
|
||||
"open": { "BaseHost": "http://base.yunhaixingtong.com",
|
||||
"BasePath": "/userbase", "SchClassPath": "/bschclass",
|
||||
"AppKey": "100002", "AppSecret": "..." }
|
||||
```
|
||||
关键:共享节里的 `BasePath` 是 **UserBase** 的路径,教务走 `SchClassPath`。
|
||||
- src 改动(`Yh.SchClass.OpenApi` 不再零依赖,引入 M.E.Options.ConfigurationExtensions + M.E.Http):
|
||||
- `SchClassOpenApiOptions`:新增 `SharedSectionName="yh:open"`、`SchClassPathKey`、`HttpClientName`
|
||||
常量;新增 `FillFromSharedSection(Func<string,string?>)` —— 只回填未显式配置项
|
||||
(BaseHost ← yh:open:BaseHost;**BasePath ← yh:open:SchClassPath**(绝不回退到 yh:open:BasePath);
|
||||
AppKey/AppSecret 同名回填),最后 NormalizeBasePath。
|
||||
- 新增 `SchClassOpenApiServiceCollectionExtensions.AddSchClassOpenApi(services, IConfiguration)`:
|
||||
Bind 独立节 `yh:schclass:open` + PostConfigure 用共享节回填 → AddHttpClient(BaseAddress+超时+SocketsHttpHandler 连接超时)→ 注册门面 Singleton。
|
||||
- `SchClassOpenApi` 新增只读属性 `HttpBaseAddress`(底层 HttpClient.BaseAddress,测试/诊断用)。
|
||||
- 测试改动:
|
||||
- `SchClassOpenApiConfigTests.cs`(6 个):SchClassPath 映射、`" /bschclass"` 空格/尾斜杠归一化、
|
||||
未配 SchClassPath 时 BasePath 保持 null(**不会误用 /userbase**)、独立节优先+共享节回填、
|
||||
DI 解析门面+BaseAddress、独立节整节生效。
|
||||
- `SchClassLiveDebug`:地址来源优先级 = 环境变量 → appsettings.json(yh:schclass:open → yh:open:SchClassPath)→ 内置默认。
|
||||
测试项目 csproj 加 `Microsoft.Extensions.Configuration(.Json)` 包 + appsettings.json CopyToOutputDirectory。
|
||||
- CPM 新增:`Microsoft.Extensions.Configuration(.Json)` 5.0.0/8.0.0。
|
||||
- 踩坑:测试项目显式引用 `Microsoft.Extensions.DependencyInjection` 8.0.0 与 M.E.Http 8.0.1 的传递依赖
|
||||
(要求 ≥8.0.1)冲突报 NU1605 包降级 → 删掉显式引用走传递依赖即可。
|
||||
- 验证:`dotnet build` 0 警告 0 错误;`dotnet test` 175 全通过(SchClass 63→69);
|
||||
联调一键跑通 20/20,联调目标确认取自 appsettings.json。
|
||||
|
||||
## 新增调试入口:Yh.SchClass.DebugTool(控制台)
|
||||
|
||||
- 动机:联调此前只能 `dotnet test --filter` 跑,需要独立可执行入口。
|
||||
- 路径:`samples/Yh.SchClass.DebugTool/`(net8.0 控制台,IsPackable=false),
|
||||
GUID `{0B1C9571-DCC3-436A-9DF6-02AB380E6CAA}`,sln 三处已注册(归入 `samples` 方案文件夹)。
|
||||
- `Program.cs`:读 appsettings.json(yh:schclass:open → yh:open 的 BaseHost+SchClassPath),
|
||||
命令行参数可覆盖:`--host --path --sch --class --tea --group --only=名称子串 --list`(列出 20 个端点名,零网络请求)。
|
||||
缺省 ID 从真实数据推导(学校列表首条 → sch;班级首条 ClassID;教师首条 UserTeaSerID)。
|
||||
输出逐端点 OK/FAIL + 条数,末尾汇总,失败退出码非 0(可接入脚本)。
|
||||
- 用法:`dotnet run --project samples/Yh.SchClass.DebugTool [-- --参数]`。
|
||||
- 踩坑:`SchClassOpenApi` 未实现 IDisposable,`using var` 报 CS1674,去掉 using 即可。
|
||||
- 验证:全方案 0 警告 0 错误;测试 175 全通过;实跑真实环境 20/20 通过。
|
||||
|
||||
## 测试工程添加 Main 运行调试入口(Yh.SchClass.OpenApi.Tests)
|
||||
|
||||
- 需求:在 IDE 里直接 F5 / `dotnet run` 测试工程即可联调,不必经 vstest。
|
||||
- 改动:
|
||||
- 测试 csproj:`<OutputType>Exe</OutputType>` + `<GenerateProgramFile>false</GenerateProgramFile>`
|
||||
(关掉 Test SDK 自动生成的空 Main,避免 CS0017;`dotnet test` 不受影响,69 个用例照常发现)。
|
||||
- `DebugMain.cs`:`DebugMain.Main(string[])` 调试入口,支持 `--only=名称子串`、`--off`;
|
||||
调 `SchClassLiveDebug.RunAllAsync`,失败退出码非 0。
|
||||
- 重构:把"一键跑通 20 个端点"的执行逻辑从 `SchClassLiveDebugTests.联调_一键跑通全部端点`
|
||||
抽成 `SchClassLiveDebug.RunAllAsync(Action<string> log, string? only = null)`,测试与 Main 共用。
|
||||
- 验证:`dotnet run --project tests/Yh.SchClass.OpenApi.Tests -- --only=VSchClass` → 3/3 通过;
|
||||
`dotnet test` → 69 全通过;全方案 0 警告 0 错误。
|
||||
|
||||
Reference in New Issue
Block a user