diff --git a/BasePlatformDotnet/Directory.Build.props b/BasePlatformDotnet/Directory.Build.props index cbddf3c..7f4befe 100644 --- a/BasePlatformDotnet/Directory.Build.props +++ b/BasePlatformDotnet/Directory.Build.props @@ -10,8 +10,9 @@ false true - 1.0.0-preview.1 + 1.0.0-preview.1:核对表 #6~#9/#12 未实调,先发预发布;实调通过再定 1.0.0 + 1.0.0-preview.2:Yh.SchClass.OpenApi 新增 SchTree(/oapi/SchGroup/GetTeaTree 教师组树) --> + 1.0.0-preview.2 $(YhPlatformNetVersion) yunhai-platform yunhai diff --git a/BasePlatformDotnet/src/Yh.SchClass.OpenApi/Model/TeaTreeVo.cs b/BasePlatformDotnet/src/Yh.SchClass.OpenApi/Model/TeaTreeVo.cs new file mode 100644 index 0000000..68fe77a --- /dev/null +++ b/BasePlatformDotnet/src/Yh.SchClass.OpenApi/Model/TeaTreeVo.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; + +namespace Yh.SchClass.OpenApi.Model; + +/// +/// 学校教师组树节点视图对象(镜像 Java com.yunhai.bschclassmanage.model.oa.TeaTreeVO)。 +/// +/// Java 侧为 Lombok @Data + @JsonInclude(NON_NULL):教师叶子节点的 +/// children 为 null 会被省略;本客户端大小写不敏感反序列化,按 Java 字段原名锚定即可双向兼容。 +/// +/// +public class TeaTreeVo +{ + /// 节点 ID(学校/组为组中心 ID,教师为 teaId,未分组兜底节点为 -1) + [JsonPropertyName("id")] public int? Id { get; set; } + + /// 节点名称 + [JsonPropertyName("name")] public string? Name { get; set; } + + /// 父节点 ID(根节点为 0) + [JsonPropertyName("pid")] public int? Pid { get; set; } + + /// 排序号(组=组中心 sort,教师=0) + [JsonPropertyName("no")] public int? No { get; set; } + + /// 节点类型(1=组节点,2=教师叶子) + [JsonPropertyName("type")] public int? Type { get; set; } + + /// 是否父节点(组=true,教师=false) + [JsonPropertyName("isParent")] public bool? IsParent { get; set; } + + /// 头像(Java 侧固定空串) + [JsonPropertyName("photo")] public string? Photo { get; set; } + + /// 子节点(组节点保证非 null;教师叶子为 null) + [JsonPropertyName("children")] public List? Children { get; set; } +} diff --git a/BasePlatformDotnet/src/Yh.SchClass.OpenApi/SchClassSchTreeClient.cs b/BasePlatformDotnet/src/Yh.SchClass.OpenApi/SchClassSchTreeClient.cs new file mode 100644 index 0000000..ee30945 --- /dev/null +++ b/BasePlatformDotnet/src/Yh.SchClass.OpenApi/SchClassSchTreeClient.cs @@ -0,0 +1,28 @@ +using Yh.SchClass.OpenApi.Model; + +namespace Yh.SchClass.OpenApi; + +/// +/// 学校教师组树客户端(镜像 Java SchGroupController,路径前缀 /oapi/SchGroup/)。 +/// +public class SchClassSchTreeClient : SchClassClientBase +{ + private const string Root = Prefix + "/SchGroup/"; + + public SchClassSchTreeClient(HttpClient httpClient, SchClassOpenApiOptions options) + : base(httpClient, options) + { + } + + /// + /// 获取学校教师组树(GET /oapi/SchGroup/GetTeaTree)。 + /// 返回单元素数组(根 = 学校节点,type=1),其下为教师组层级挂教师叶子(type=2)。 + /// 与 至少提供一个(服务端 schId 优先), + /// 都缺时服务端返回空数组(本方法返回空列表,与 Java 行为一致)。 + /// + /// 学校外部序列号(Java 参数名 SchSerID)。 + /// 学校内部 ID(Java 参数名 schId,优先使用)。 + public Task> GetTeaTreeAsync(string? schSerId = null, int? schId = null, CancellationToken ct = default) + => ResolveListAsync(HttpMethod.Get, Root + "GetTeaTree", + Params(("SchSerID", schSerId), ("schId", schId)), ct: ct); +} diff --git a/BasePlatformDotnet/tests/Yh.SchClass.OpenApi.Tests/SchClassSchTreeClientTests.cs b/BasePlatformDotnet/tests/Yh.SchClass.OpenApi.Tests/SchClassSchTreeClientTests.cs new file mode 100644 index 0000000..f6457d6 --- /dev/null +++ b/BasePlatformDotnet/tests/Yh.SchClass.OpenApi.Tests/SchClassSchTreeClientTests.cs @@ -0,0 +1,101 @@ +namespace Yh.SchClass.OpenApi.Tests; + +/// SchTree(教师组树)端点契约与 反序列化。 +public class SchClassSchTreeClientTests +{ + private const string Root = "/oapi/SchGroup/"; + + [Fact] + public async Task SchGroup_GetTeaTree_路径与参数名对齐() + { + var handler = SchClassTestHost.Ok(); + var api = SchClassTestHost.Api(handler); + + // 双参数齐全:按 Java 参数原名 SchSerID / schId 提交 + await api.SchTree.GetTeaTreeAsync("S1", 2); + SchClassTestHost.Verify(handler, HttpMethod.Get, Root + "GetTeaTree", "?SchSerID=S1&schId=2"); + + // 仅 schId(服务端优先使用) + await api.SchTree.GetTeaTreeAsync(null, 5); + SchClassTestHost.Verify(handler, HttpMethod.Get, Root + "GetTeaTree", "?schId=5"); + + // 仅 SchSerID + await api.SchTree.GetTeaTreeAsync("S9", null); + SchClassTestHost.Verify(handler, HttpMethod.Get, Root + "GetTeaTree", "?SchSerID=S9"); + + // 都缺:服务端返回 [](不抛异常) + await api.SchTree.GetTeaTreeAsync(); + SchClassTestHost.Verify(handler, HttpMethod.Get, Root + "GetTeaTree", ""); + } + + [Fact] + public async Task TeaTreeVO_嵌套树按Java原名反序列化() + { + // 教师叶子节点无 children 字段(Java @JsonInclude(NON_NULL) 省略) + var handler = FakeHandler.Json(HttpStatusCode.OK, """ + [{"id":10,"name":"第一中学","pid":0,"no":1,"type":1,"isParent":true,"photo":"", + "children":[ + {"id":31,"name":"教师组","pid":10,"no":1,"type":1,"isParent":true,"photo":"", + "children":[ + {"id":32,"name":"语文组","pid":31,"no":1,"type":1,"isParent":true,"photo":"", + "children":[ + {"id":77,"name":"王老师","pid":32,"no":0,"type":2,"isParent":false,"photo":""}]}, + {"id":99,"name":"李老师","pid":31,"no":0,"type":2,"isParent":false,"photo":""}]}]}] + """); + + var tree = await SchClassTestHost.Api(handler).SchTree.GetTeaTreeAsync("S001", null); + + Assert.Single(tree); + var sch = tree[0]; + Assert.Equal(10, sch.Id); + Assert.Equal("第一中学", sch.Name); + Assert.Equal(0, sch.Pid); + Assert.Equal(1, sch.Type); + Assert.True(sch.IsParent); + Assert.Equal("", sch.Photo); + + var teaGroup = Assert.Single(sch.Children!); + Assert.Equal(31, teaGroup.Id); + Assert.Equal("教师组", teaGroup.Name); + Assert.Equal(10, teaGroup.Pid); + + Assert.Equal(2, teaGroup.Children!.Count); + var subject = teaGroup.Children[0]; + Assert.Equal("语文组", subject.Name); + var tea = Assert.Single(subject.Children!); + Assert.Equal(77, tea.Id); + Assert.Equal("王老师", tea.Name); + Assert.Equal(32, tea.Pid); + Assert.Equal(0, tea.No); + Assert.Equal(2, tea.Type); + Assert.False(tea.IsParent); + Assert.Null(tea.Children); + + var ungrouped = teaGroup.Children[1]; + Assert.Equal(99, ungrouped.Id); + Assert.Equal("李老师", ungrouped.Name); + Assert.Null(ungrouped.Children); + } + + [Fact] + public async Task GetTeaTree_两参数都缺时服务端空数组返回空列表() + { + var handler = FakeHandler.Json(HttpStatusCode.OK, "[]"); + + var tree = await SchClassTestHost.Api(handler).SchTree.GetTeaTreeAsync(); + + Assert.NotNull(tree); + Assert.Empty(tree); + } + + [Fact] + public async Task GetTeaTree_服务端404时归一为空列表() + { + var handler = FakeHandler.Empty(HttpStatusCode.NotFound); + + var tree = await SchClassTestHost.Api(handler).SchTree.GetTeaTreeAsync("S001", null); + + Assert.NotNull(tree); + Assert.Empty(tree); + } +} diff --git a/yh-platform-dotnet建设方案v4.md b/yh-platform-dotnet建设方案v4.md index 9ab204c..052c0a5 100644 --- a/yh-platform-dotnet建设方案v4.md +++ b/yh-platform-dotnet建设方案v4.md @@ -245,7 +245,7 @@ dotnet pack -c Release -o artifacts # 预期 4 包生成 | 模型契约 | ✅ 13 POCO ⇔ 权威快照双向对拍 | | 双序列化器 | ✅ Newtonsoft(大小写不敏感)+ STJ Web 逐节点对拍 | | pack 产物 | ✅ 5 包(含 Yh.SchClass.OpenApi)、双 TFM lib、依赖分组正确(本地验证) | -| Nexus 发布 | ✅ 5 包 1.0.0-preview.1 已推送(2026-09-02);CI #643 全绿自动发布链路打通 | +| 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 发布) |