feat: SchTree 客户端对接 SchGroup/GetTeaTree,升版 1.0.0-preview.2
ci / build-test (push) Successful in 3m0s
ci / publish (push) Successful in 14m30s

- 补齐 4226b04 未入库的 3 个新文件(TeaTreeVo/SchClassSchTreeClient/契约测试),修复该提交编译不过
- 版本升至 1.0.0-preview.2:Nexus nuget-hosted 禁止重传(ALLOW_ONCE),同版本不递增会被查重跳过
This commit is contained in:
wanghu
2026-09-08 16:36:33 +08:00
parent 4226b042a8
commit 8896b29c4d
5 changed files with 170 additions and 3 deletions
+3 -2
View File
@@ -10,8 +10,9 @@
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<IsPackable>true</IsPackable>
<!-- 包版本统一随 YhPlatformNetVersion 走(对齐 BOM 集中钉版本)
1.0.0-preview.1:核对表 #6~#9/#12 未实调,先发预发布;实调通过再定 1.0.0 -->
<YhPlatformNetVersion>1.0.0-preview.1</YhPlatformNetVersion>
1.0.0-preview.1:核对表 #6~#9/#12 未实调,先发预发布;实调通过再定 1.0.0
1.0.0-preview.2:Yh.SchClass.OpenApi 新增 SchTree(/oapi/SchGroup/GetTeaTree 教师组树) -->
<YhPlatformNetVersion>1.0.0-preview.2</YhPlatformNetVersion>
<Version>$(YhPlatformNetVersion)</Version>
<Authors>yunhai-platform</Authors>
<Company>yunhai</Company>
@@ -0,0 +1,37 @@
using System.Text.Json.Serialization;
namespace Yh.SchClass.OpenApi.Model;
/// <summary>
/// 学校教师组树节点视图对象(镜像 Java <c>com.yunhai.bschclassmanage.model.oa.TeaTreeVO</c>)。
/// <para>
/// Java 侧为 Lombok <c>@Data</c> + <c>@JsonInclude(NON_NULL)</c>:教师叶子节点的
/// <c>children</c> 为 null 会被省略;本客户端大小写不敏感反序列化,按 Java 字段原名锚定即可双向兼容。
/// </para>
/// </summary>
public class TeaTreeVo
{
/// <summary>节点 ID(学校/组为组中心 ID,教师为 teaId,未分组兜底节点为 -1)</summary>
[JsonPropertyName("id")] public int? Id { get; set; }
/// <summary>节点名称</summary>
[JsonPropertyName("name")] public string? Name { get; set; }
/// <summary>父节点 ID(根节点为 0)</summary>
[JsonPropertyName("pid")] public int? Pid { get; set; }
/// <summary>排序号(组=组中心 sort,教师=0)</summary>
[JsonPropertyName("no")] public int? No { get; set; }
/// <summary>节点类型(1=组节点,2=教师叶子)</summary>
[JsonPropertyName("type")] public int? Type { get; set; }
/// <summary>是否父节点(组=true,教师=false)</summary>
[JsonPropertyName("isParent")] public bool? IsParent { get; set; }
/// <summary>头像(Java 侧固定空串)</summary>
[JsonPropertyName("photo")] public string? Photo { get; set; }
/// <summary>子节点(组节点保证非 null;教师叶子为 null)</summary>
[JsonPropertyName("children")] public List<TeaTreeVo>? Children { get; set; }
}
@@ -0,0 +1,28 @@
using Yh.SchClass.OpenApi.Model;
namespace Yh.SchClass.OpenApi;
/// <summary>
/// 学校教师组树客户端(镜像 Java <c>SchGroupController</c>,路径前缀 <c>/oapi/SchGroup/</c>)。
/// </summary>
public class SchClassSchTreeClient : SchClassClientBase
{
private const string Root = Prefix + "/SchGroup/";
public SchClassSchTreeClient(HttpClient httpClient, SchClassOpenApiOptions options)
: base(httpClient, options)
{
}
/// <summary>
/// 获取学校教师组树(<c>GET /oapi/SchGroup/GetTeaTree</c>)。
/// 返回单元素数组(根 = 学校节点,type=1),其下为教师组层级挂教师叶子(type=2)。
/// <paramref name="schId"/> 与 <paramref name="schSerId"/> 至少提供一个(服务端 schId 优先),
/// 都缺时服务端返回空数组(本方法返回空列表,与 Java 行为一致)。
/// </summary>
/// <param name="schSerId">学校外部序列号(Java 参数名 <c>SchSerID</c>)。</param>
/// <param name="schId">学校内部 ID(Java 参数名 <c>schId</c>,优先使用)。</param>
public Task<List<TeaTreeVo>> GetTeaTreeAsync(string? schSerId = null, int? schId = null, CancellationToken ct = default)
=> ResolveListAsync<TeaTreeVo>(HttpMethod.Get, Root + "GetTeaTree",
Params(("SchSerID", schSerId), ("schId", schId)), ct: ct);
}
@@ -0,0 +1,101 @@
namespace Yh.SchClass.OpenApi.Tests;
/// <summary>SchTree(教师组树)端点契约与 <see cref="TeaTreeVo"/> 反序列化。</summary>
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);
}
}
+1 -1
View File
@@ -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 发布) |