父 Issue:#3(M1 自拆解简报 · 入口级交付)· WP3
Blocked by #5(已认证主体)
Blocked by HashMatrixData/hashmatrix#18(契约先行:control-plane-v1 加 /me/tenants)
目标
新增 GET /me/tenants(应用内 /api/v1/me/tenants,网关 strip /api 后对齐契约 /v1/me/tenants):返回当前登录用户的租户 membership 列表。M1 先返回 1 个,但 DTO/契约一律按数组,为 D1 多租户预留。
约束(不可返工)
- DTO 为数组(
List<MembershipView>),即便 M1 只 1 个 membership。
- 对齐 D1(单 Keycloak User + 多 Org Membership)/ D2(切换租户 = 重新换 token,
X-Tenant-Id 唯一)。
- 「当前用户」= SecurityContext 主体(由 WP2 的
GatewayPreAuthFilter 据 X-User 填充);不得裸读 header 绕过 starter(过渡债反模式)。
契约先行(铁律 · 跨仓)
/me/tenants 当前不在 contracts/openapi/control-plane-v1.yaml。按「先改契约再改实现」,须先合主仓契约 HashMatrixData/hashmatrix#18,本 WP 再落实现并保持一致。
范围(文件清单)
- 新增
tenant/web/MeController.java:类级 @RequestMapping("/api/v1/me") + @GetMapping("/tenants") → 应用内 /api/v1/me/tenants(网关 strip /api → 契约 /v1/me/tenants)。承载当前主体 Authentication。
- 🛑 不得塞进
TenantController:其类级是 @RequestMapping("/api/v1/tenants"),在内加 @GetMapping("/me") 只会得到 /api/v1/tenants/me;而契约 /v1/me/tenants 的 me/tenants 是独立资源根、不在 /tenants 下 → 破契约一致性 + webui 消费方路径错配。故必须新开 MeController。
tenant/service/TenantService.java:+ 按当前主体查 membership(M1 可由 adminEmail 派生 1 个;模型预留多个)。
- 新 DTO
tenant/web/dto/MembershipView.java(数组元素:tenantId/tenantKey/displayName/status)。
- 单测(断言路径
/api/v1/me/tenants,非 /api/v1/tenants/me)。
起点(精确路径 + 真/stub/缺失)
- 缺失:端点、service 查询、DTO。
- 现状:无 membership/user-tenant 关联表;M1 以「当前用户 = 某租户 adminEmail」派生单元素数组(不新建表,模型层预留多对多)。
预估 LOC
~90–130。
测试
- 带 superadmin/普通用户头 → 返回当前主体 membership 数组(M1 1 个元素)。
- 无头 → 401(沿用 WP2 过滤链)。
依赖
- Blocked by:WP2(主体)+ 主仓契约 issue(
/me/tenants 先行)。
- Blocks:WP5。
可独立 ship
⚠️ 加法端点、默认安全;但须 WP2 与主仓契约就绪后方可 merge。
验收
目标
新增
GET /me/tenants(应用内/api/v1/me/tenants,网关 strip/api后对齐契约/v1/me/tenants):返回当前登录用户的租户 membership 列表。M1 先返回 1 个,但 DTO/契约一律按数组,为 D1 多租户预留。约束(不可返工)
List<MembershipView>),即便 M1 只 1 个 membership。X-Tenant-Id唯一)。GatewayPreAuthFilter据X-User填充);不得裸读 header 绕过 starter(过渡债反模式)。契约先行(铁律 · 跨仓)
/me/tenants当前不在contracts/openapi/control-plane-v1.yaml。按「先改契约再改实现」,须先合主仓契约 HashMatrixData/hashmatrix#18,本 WP 再落实现并保持一致。范围(文件清单)
tenant/web/MeController.java:类级@RequestMapping("/api/v1/me")+@GetMapping("/tenants")→ 应用内/api/v1/me/tenants(网关 strip/api→ 契约/v1/me/tenants)。承载当前主体Authentication。TenantController:其类级是@RequestMapping("/api/v1/tenants"),在内加@GetMapping("/me")只会得到/api/v1/tenants/me;而契约/v1/me/tenants的me/tenants是独立资源根、不在/tenants下 → 破契约一致性 + webui 消费方路径错配。故必须新开MeController。tenant/service/TenantService.java:+ 按当前主体查 membership(M1 可由adminEmail派生 1 个;模型预留多个)。tenant/web/dto/MembershipView.java(数组元素:tenantId/tenantKey/displayName/status)。/api/v1/me/tenants,非/api/v1/tenants/me)。起点(精确路径 + 真/stub/缺失)
预估 LOC
~90–130。
测试
依赖
/me/tenants先行)。可独立 ship
验收
GET /api/v1/me/tenants(网关后/v1/me/tenants)返回数组(M1 先 1 个),形状对齐主仓契约 hashmatrix#18。MeController承载(路径/api/v1/me/tenants,非/api/v1/tenants/me)。