Files
th-hotel-simple/docs/project/requirements/M005-hotel-context-unification-plan.md
2026-07-10 15:31:51 +08:00

21 KiB
Raw Blame History

M005 Hotel Context 统一收口改造计划

文档信息

项目 内容
文档版本 0.2
日期 2026-07-10
状态 已落地第一版:单酒店阶段严格要求 platform_hotel 必须且只能有一家 ACTIVE 酒店
适用范围 hotel_id 来源、用户酒店上下文、SuperAgent / MCP、AgentBus、Reservation 查询、SourceMessage 查询、Debug / Demo 入口
主要读者 产品、后端、前端、测试、后续协作 agent

1. 文档定位

本文记录当前系统 hotel_id 使用方式的统一收口方案。目标不是删除业务表里的 hotel_id,而是把运行时 hotel_id 的来源从“外部系统传入、前端环境变量、后端硬编码和配置默认值混用” 调整为“后端从平台酒店表和当前用户上下文解析”。

当前已明确:

  • SuperAgent 不会传 hotel_id
  • AgentBus 不会传 hotel_id
  • 本系统上线前暂时只服务一家酒店。
  • 后端已经有平台酒店表 platform_hotel 和用户酒店授权表 platform_user_hotel
  • 登录态第一版采用数据库 session token不使用 JWTtoken 字符串本身不携带酒店 ID。
  • 单酒店阶段采用严格规则:platform_hotel 没有 ACTIVE 酒店时报错,多家 ACTIVE 酒店也报错;数据库通过唯一约束阻止新增第二家 ACTIVE 酒店。

因此,后续业务入口不应再要求 SuperAgent、AgentBus 或前端环境变量作为酒店上下文事实来源。 系统内部仍然必须保留 hotel_id,用于数据隔离、幂等键、查询过滤、审计和未来多酒店扩展。

2. 核心结论

这次想法与现有系统没有根本冲突,可以实现,但需要把现有几个来源不一致的入口统一改造。

关键判断如下:

  • hotel_id 字段继续保留在 SourceMessage、Reservation、Task、OPERA、审计等业务表中。
  • 外部系统不传 hotel_id 是合理的,酒店归属应由本系统后端根据平台酒店表解析。
  • token 不是 JWT不会把 hotel_id 放在 token 字符串里;后端可通过 session token 解析出 AuthenticatedUserContext.defaultHotelIdaccessibleHotelIds
  • 前端展示酒店已经可以从 /api/auth/me 返回的 hotels[]default_hotel_id 获取Reservation 查询默认不再依赖 VITE_RESERVATION_HOTEL_ID 自动拼查询参数。
  • 任务详情、任务写操作和 OPERA 操作当前多处已从任务自身读取 task.hotelId(),这类逻辑方向正确, 后续重点是补权限校验和查询隔离,不需要前端再传 hotel_id

3. 非目标范围

本次 M005 不做以下事情:

  • 不删除任何业务表中的 hotel_id 字段。
  • 不把 token 改成 JWT。
  • 不实现完整多酒店路由策略例如按邮箱、AgentBus channel、SuperAgent org 或 OHIP 配置自动映射酒店。
  • 不新增酒店管理后台 CRUD。
  • 不改变 SuperAgent 和 AgentBus 的系统定位AgentBus 仍是消息入口适配器SuperAgent 仍是 AI / Agent 能力提供方。
  • 不让前端直接调用数据库、SuperAgent、AgentBus 或任何持有 Secret 的外部系统。

4. 当前现状梳理

入口或模块 当前 hotel_id 来源 问题 目标方向
AgentBus 入站捕获 改造前使用 agentbus.capture.default-hotel-id,代码默认 HOTEL-TEST AgentBus 不会传酒店,配置默认值和平台酒店表割裂 已改为从平台酒店表解析系统酒店
SuperAgent 任务结果 REST 改造前 JSON 正式请求要求 hotel_idS000 / S999 文本请求使用 AgentBus 默认酒店 SuperAgent 不会传酒店,正式契约与真实能力冲突 已支持缺省 hotel_id 使用系统酒店,再用 SourceMessage 自身酒店写业务表
SuperAgent MCP 查询工具 改造前 tools schema 要求 hotel_id SuperAgent MCP 配置页面和调用方不应承担酒店上下文 已将 hotel_id 改为可选,服务端解析系统酒店
Reservation AI 查询服务 改造前 ReservationAiQueryServiceImpl 校验 hotel_id 必填 机器查询入口无法在无酒店参数时工作 已统一通过酒店上下文服务解析
前端 Reservation 列表 / 详情 改造前 VITE_RESERVATION_HOTEL_ID 自动拼 hotel_id 前端环境变量成为业务事实来源,容易和平台酒店表不一致 已改为默认不传;如有当前选中酒店才传,后端校验
任务详情 / 任务写操作 / OPERA 先按 taskId 找任务,再使用 task.hotelId() 方向正确,但后续需要当前用户可访问酒店校验 保持从业务对象自身取酒店,并补权限边界
SourceMessage 列表 改造前 hotelId 查询参数可选;为空时仓储不加酒店过滤 单酒店阶段可能还能工作,未来会有跨酒店暴露风险 已默认解析当前或系统酒店,不允许无边界列表
SourceMessage 会话详情 先按内部 SourceMessage ID 找源消息,再用 source.hotelId() 查同会话 方向正确 保持源消息自身酒店上下文
Debug EML 改造前 multipart 必填 hotel_id 测试人员需要手填,和平台酒店表割裂 已改为可选,缺省使用系统酒店,显式传值必须校验
Demo seed 改造前请求可传 hotel_id,否则使用 demo 配置默认值,默认 HOTEL-TEST 演示数据入口也有独立默认来源 已改为系统酒店,返回入口 URL 不再强依赖手写酒店参数
登录 / 当前用户 /api/auth/me 已返回 default_hotel_idhotels[] 后端业务接口尚未统一消费当前用户酒店上下文 作为用户请求的酒店上下文来源

5. 目标酒店上下文模型

建议新增一个平台级服务,统一承担酒店上下文解析。命名可在实现时二选一:

cn.nianxx.thhotel.platform.hotel.service.HotelContextService
cn.nianxx.thhotel.platform.hotel.service.impl.HotelContextServiceImpl

或:

cn.nianxx.thhotel.platform.hotel.service.HotelContextResolver
cn.nianxx.thhotel.platform.hotel.service.impl.HotelContextResolverImpl

职责建议:

方法 使用场景 规则
resolveSystemHotelId() AgentBus、SuperAgent、MCP、Debug、Demo 等机器入口 platform_hotel 解析当前系统酒店;单酒店阶段要求只有一个启用酒店,或存在明确默认酒店
resolveCurrentHotelId(String requestedHotelId) 前端用户查询入口 有 token 时优先校验请求酒店是否在 accessibleHotelIds;请求为空时使用 defaultHotelId
requireAccessibleHotel(String hotelId) 后续写操作、详情操作、审计查询 当前用户必须可访问该酒店;无 token 的兼容策略需单独声明

单酒店阶段建议使用严格规则:

  1. 如果平台酒店表没有启用酒店,启动或首次调用时返回明确错误,例如 SYSTEM_HOTEL_NOT_CONFIGURED
  2. 如果平台酒店表存在多家启用酒店,但没有明确默认酒店,返回明确错误,例如 SYSTEM_HOTEL_AMBIGUOUS
  3. 当前阶段不要继续用 HOTEL-TEST 作为运行时兜底值;HOTEL-TEST 只能保留在测试夹具、示例文档或 bootstrap 默认配置中。

6. 各入口目标规则

6.1 AgentBus 入站捕获

当前代码位置:

  • server/src/main/java/cn/nianxx/thhotel/integrations/messaging/agentbus/adapter/AgentBusFrameProcessor.java
  • server/src/main/java/cn/nianxx/thhotel/integrations/messaging/agentbus/adapter/AgentBusProperties.java
  • server/src/main/java/cn/nianxx/thhotel/platform/message/service/impl/SourceMessageCaptureServiceImpl.java

目标规则:

  • AgentBusFrameProcessor 不再从 AgentBusProperties.capture.defaultHotelId 取酒店。
  • 捕获前调用 HotelContextService.resolveSystemHotelId()
  • SourceMessageCaptureServiceImpl 仍要求 CaptureSourceMessageCommand.hotelId 非空,因为落库必须有稳定酒店上下文。
  • agentbus.capture.default-hotel-id 后续可以废弃,或仅作为临时兼容配置,优先级低于平台酒店表。

6.2 SuperAgent 任务结果 REST

当前代码位置:

  • server/src/main/java/cn/nianxx/thhotel/integrations/ai/superagent/control/SuperAgentTaskResultController.java
  • server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationAiTaskIntakeServiceImpl.java

目标规则:

  • JSON 正式请求不再要求 SuperAgent 传 hotel_id
  • 当请求体没有 hotel_id 时,后端使用 resolveSystemHotelId() 查 SourceMessage。
  • 命中 SourceMessage 后,后续 Reservation 批次、订单、任务、审计都继续使用 sourceMessage.hotelId(),不直接信任外部请求体。
  • 如果外部请求体仍传了 hotel_id,单酒店阶段建议校验它必须等于系统酒店;不一致时返回明确错误,避免静默写错酒店。
  • 旧的“无 hotel_id 时按内部 SourceMessage ID 查询”的本地兼容路径应继续限制为 dev / test 或明确标注为非正式契约。

6.3 SuperAgent MCP

当前代码位置:

  • server/src/main/java/cn/nianxx/thhotel/integrations/mcp/superagent/service/impl/SuperAgentMcpServiceImpl.java
  • docs/project/integrations/superagent-mcp/tools.md
  • docs/project/integrations/superagent-mcp/test-cases.md

目标规则:

  • MCP tools schema 中的 hotel_idrequired 移除。
  • 查询类工具在参数缺少 hotel_id 时,由 MCP 服务端补入系统酒店。
  • 写入类工具 submit_ai_task_results 也不再要求 hotel_id,但仍必须要求 source_message_id 和结果列表。
  • 文档中应明确SuperAgent 不需要知道酒店 ID酒店上下文由 TH Hotel 后端托管。
  • 现有 HOTEL_ID_REQUIRED 的 MCP 测试用例要改成“不传 hotel_id 仍成功或进入业务查询缺失 key 错误”。

6.4 Reservation AI 查询服务

当前代码位置:

  • server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationAiQueryServiceImpl.java
  • server/src/main/java/cn/nianxx/thhotel/workflows/reservation/common/request/ReservationAiCaseContextQueryRequest.java
  • server/src/main/java/cn/nianxx/thhotel/workflows/reservation/common/request/ReservationAiObjectDetailQueryRequest.java
  • server/src/main/java/cn/nianxx/thhotel/workflows/reservation/common/request/ReservationMessageConversationQueryRequest.java

目标规则:

  • hotel_id 在请求 DTO 中保留,但语义改为可选。
  • 进入查询服务时统一调用酒店上下文服务解析有效酒店 ID。
  • 查询仓储层继续显式按 hotel_id 过滤,保证业务数据隔离。
  • 对 SuperAgent / MCP 机器入口,缺省酒店来自系统酒店。
  • 对用户入口,缺省酒店来自当前用户默认酒店,显式酒店必须通过可访问酒店校验。

6.5 前端 Reservation 查询

当前代码位置:

  • client/src/services/reservationService.ts
  • client/src/config/reservationConfig.ts
  • client/src/stores/authStore.ts
  • client/src/layouts/ReservationAppShell.vue

目标规则:

  • 前端酒店展示以 authStore.hotelsauthStore.defaultHotelIdauthStore.selectedHotelId 为准。
  • Reservation 列表、任务列表、订单详情默认不再依赖 VITE_RESERVATION_HOTEL_ID
  • 单酒店阶段可以默认不传 hotel_id,由后端按当前用户上下文解析。
  • 如果保留酒店选择器,前端传当前选中的 selectedHotelId,后端必须校验该酒店在当前用户可访问列表中。
  • VITE_RESERVATION_HOTEL_ID 后续只适合保留为 fixture / 本地兼容配置,不作为真实业务上下文。

6.6 前端 Reservation 查询 Controller

当前代码位置:

  • server/src/main/java/cn/nianxx/thhotel/workflows/reservation/control/ReservationFrontendQueryController.java
  • server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationFrontendQueryServiceImpl.java

目标规则:

  • Controller 可以继续接收可选 hotel_id,用于未来用户切换酒店。
  • Service 不再用 DEFAULT_HOTEL_ID = "HOTEL-TEST" 兜底。
  • Service 调用 resolveCurrentHotelId(request.hotelId()) 得到有效酒店 ID。
  • 没有登录态的兼容行为需要明确:建议 dev / test 可回退系统酒店,生产逐步要求 token。

6.7 SourceMessage 查询

当前代码位置:

  • server/src/main/java/cn/nianxx/thhotel/platform/message/control/SourceMessageController.java
  • server/src/main/java/cn/nianxx/thhotel/platform/message/repository/MybatisSourceMessageInboxRepository.java
  • server/src/main/java/cn/nianxx/thhotel/platform/message/service/impl/SourceMessageConversationServiceImpl.java

目标规则:

  • SourceMessage 列表接口不应在 hotelId 为空时返回跨酒店结果。
  • 列表接口缺省酒店时调用 resolveCurrentHotelId(null)resolveSystemHotelId(),具体取决于是否要求登录。
  • 会话详情和原文读取已经能从 SourceMessage 自身获取 source.hotelId(),方向正确;后续只补当前用户是否可访问该酒店的校验。

6.8 Debug EML 与 Demo Seed

当前代码位置:

  • server/src/main/java/cn/nianxx/thhotel/platform/debug/control/DebugEmlSuperAgentController.java
  • server/src/main/java/cn/nianxx/thhotel/platform/debug/service/impl/DebugEmlSuperAgentRunServiceImpl.java
  • server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationDemoDataServiceImpl.java
  • server/src/main/java/cn/nianxx/thhotel/workflows/reservation/service/impl/ReservationDemoDataProperties.java

目标规则:

  • Debug EML 的 hotel_id 从必填改为可选。
  • dev / test 缺省时使用系统酒店。
  • 如果调试人员显式传 hotel_id,后端校验它必须存在于平台酒店表;生产不建议开放该覆盖能力。
  • Demo seed 缺省酒店改为系统酒店,不再有独立 defaultHotelId = "HOTEL-TEST" 运行时兜底。

7. 配置调整建议

配置 当前用途 调整建议
AUTH_*_BOOTSTRAP_DEFAULT_HOTEL_ID 初始化平台酒店和管理员默认酒店 保留,用于 bootstrap不作为每次请求的运行时兜底
AGENTBUS_*_DEFAULT_HOTEL_ID 旧 AgentBus fallback 第一版运行时已不再使用,后续清理配置文件和环境模板
RESERVATION_*_DEMO_DATA_DEFAULT_HOTEL_ID 旧 Demo seed 默认酒店 第一版运行时已不再使用,后续清理配置文件和环境模板
VITE_RESERVATION_HOTEL_ID 前端 Reservation 默认酒店 从真实联调和生产配置中移除,保留 fixture / 本地兼容说明

配置文件调整原则:

  • application-test.ymlapplication-prod.yml 不应继续写死运行时端口、数据库、酒店 ID 等会由环境覆盖的值。
  • 平台酒店初始化可以继续通过 bootstrap env 控制,但业务请求解析应查平台酒店表。
  • 如果系统酒店无法解析,应快速失败并给出明确错误,而不是默默回到 HOTEL-TEST

8. 分阶段落地计划

Phase 1酒店上下文服务底座

修改范围:

  • 新增 platform.hotel.service.HotelContextService
  • 新增 platform.hotel.service.impl.HotelContextServiceImpl
  • 扩展 PlatformHotelRepository,支持解析启用酒店和默认酒店。
  • 为系统酒店解析、当前用户默认酒店、无酒店、多酒店歧义写单元测试。

验收标准:

  • 单启用酒店时可解析系统酒店。
  • 无启用酒店时报 SYSTEM_HOTEL_NOT_CONFIGURED
  • 多启用酒店且无默认规则时报 SYSTEM_HOTEL_AMBIGUOUS
  • 当前用户有默认酒店时,用户请求解析使用默认酒店。
  • 显式请求酒店不在当前用户可访问列表时拒绝。

Phase 2机器入口收口

修改范围:

  • AgentBus 捕获改用 resolveSystemHotelId()
  • SuperAgent REST 入站缺省 hotel_id 时使用系统酒店。
  • MCP tools schema 移除 hotel_id required并由服务端补酒店。
  • Reservation AI 查询服务把 hotel_id 必填校验改成上下文解析。

验收标准:

  • AgentBus 入站 frame 不带 hotel_id 也能入库 SourceMessage。
  • SuperAgent JSON 任务结果不带 hotel_id 也能通过外部 source_message_id 找到 SourceMessage。
  • MCP 查询工具不传 hotel_id 也能执行。
  • 业务写入最终使用 SourceMessage 自身 hotelId

Phase 3用户入口和前端收口

修改范围:

  • Reservation 前端查询服务不再默认拼 VITE_RESERVATION_HOTEL_ID
  • Reservation 后端查询服务移除 DEFAULT_HOTEL_ID = "HOTEL-TEST"
  • SourceMessage 列表接口缺省酒店时按当前用户或系统酒店过滤。
  • 任务详情、任务写操作、OPERA 操作增加当前用户可访问酒店校验。

验收标准:

  • 登录后前端酒店名称来自 /api/auth/mehotels[]
  • Reservation 列表、任务列表、订单详情在不传 hotel_id 时也能按默认酒店查询。
  • 用户显式切换酒店时,后端校验酒店访问权限。
  • SourceMessage 列表不会因为缺少 hotelId 返回跨酒店数据。

Phase 4配置、文档和兼容清理

修改范围:

  • 更新 README.mdVITE_RESERVATION_HOTEL_ID 的说明。
  • 更新 SuperAgent API / MCP 文档,明确外部系统不需要传 hotel_id
  • 更新 go-live notes移除“正式 JSON 请求必须带 hotel_id”的上线要求。
  • 清理 application-*.yml 中和运行时默认酒店相关的硬编码。
  • 保留测试夹具里的 HOTEL-TEST,但标注为测试数据。

验收标准:

  • 文档与真实契约一致。
  • prod / test 启动依赖平台酒店表,不依赖散落默认酒店配置。
  • 搜索运行时代码时,不再存在作为兜底逻辑的 DEFAULT_HOTEL_ID = "HOTEL-TEST"

9. 数据和迁移注意事项

上线前需要确认:

  • platform_hotel 中存在当前酒店的启用记录。
  • 单酒店阶段 platform_hotel 只能有一家 ACTIVE 酒店;迁移脚本 V12__enforce_single_active_platform_hotel.sql 会通过唯一索引阻止第二家 ACTIVE 酒店。
  • 现有 SourceMessage、Reservation、Task、OPERA、审计数据的 hotel_idplatform_hotel.hotel_id 一致。
  • 如果现有数据使用 HOTEL-TEST,而正式平台酒店 ID 不是 HOTEL-TEST,需要先设计数据迁移脚本。
  • 管理员用户在 platform_user_hotel 中有当前酒店默认授权,或超级管理员可访问全部启用酒店。
  • SuperAgent / MCP 文档和配置页面中的服务地址、鉴权 token、工具 schema 已同步更新。

建议先在测试环境执行检查:

SELECT hotel_id, hotel_name, hotel_status, time_zone
FROM platform_hotel
ORDER BY sort_order, id;

SELECT hotel_id, COUNT(*) AS source_message_count
FROM platform_source_message_inbox
GROUP BY hotel_id;

SELECT hotel_id, COUNT(*) AS reservation_task_count
FROM workflow_reservation_task
GROUP BY hotel_id;

中文说明:第一条确认平台酒店表;第二条和第三条确认已有消息与任务数据的酒店 ID 是否和平台酒店表一致。

10. 测试建议

后端建议覆盖:

  • HotelContextServiceImplTest
  • AgentBusFrameProcessorTest
  • SuperAgentTaskResultControllerTest
  • ReservationAiTaskIntakeServiceImplTest
  • SuperAgentMcpServiceImplTest
  • ReservationAiQueryServiceImplTest
  • ReservationFrontendQueryServiceImplTest
  • SourceMessageController 或相关查询服务测试

前端建议覆盖:

  • reservationService.spec.ts:不再默认拼 VITE_RESERVATION_HOTEL_ID,或改为使用当前选中酒店。
  • authStore.spec.ts:继续验证默认酒店和可访问酒店选择逻辑。
  • reservationAppShell.spec.ts:酒店展示来自登录上下文。

集成验收建议:

  • AgentBus frame 入站不带酒店字段SourceMessage 落库有正确 hotel_id
  • SuperAgent 任务结果 JSON 不带 hotel_id,仍能写入正确酒店下的订单和任务。
  • MCP tools/call 不带 hotel_id,仍可查询订单上下文。
  • 前端登录后不配置 VITE_RESERVATION_HOTEL_ID,订单列表和任务列表仍可查询。
  • 非授权用户访问其他酒店数据时被拒绝。

11. 当前决策和后置事项

已确认并落地:

  1. 单酒店阶段“系统酒店”采用严格规则:platform_hotel 必须且只能有一家 ACTIVE 酒店。
  2. 如果机器入口或前端兼容旧契约显式传入 hotel_id,后端严格校验它必须等于系统酒店或当前用户可访问酒店。
  3. SuperAgent、AgentBus、MCP tools 和 Debug EML 默认都不需要传 hotel_id
  4. HOTEL-TEST 只能保留在测试夹具、示例文档或 bootstrap 默认配置中,不作为业务运行时兜底酒店。

后置事项:

  1. 生产环境是否强制 Reservation / SourceMessage 用户接口必须带登录 token等权限拦截策略整体收口时再确认。
  2. 如果正式酒店 ID 不是 HOTEL-TEST,需要先迁移测试机或存量数据里的 hotel_id
  3. AGENTBUS_*_DEFAULT_HOTEL_IDRESERVATION_*_DEMO_DATA_DEFAULT_HOTEL_ID 等旧配置项后续可以从配置模板中清理;本次第一版先保证运行时代码不再依赖它们。

12. 最终验收口径

M005 完成后,应满足以下口径:

  • SuperAgent 和 AgentBus 不传 hotel_id,系统仍能正常入库、查询和写入 Reservation 任务。
  • 前端酒店展示来自平台酒店表和当前登录用户上下文。
  • 前端 Reservation 查询不依赖 VITE_RESERVATION_HOTEL_ID 作为真实业务上下文。
  • 后端所有查询和写入最终都带有明确 hotel_id 过滤或落库值。
  • hotel_id 的运行时来源统一为平台酒店表或当前用户上下文。
  • 运行时代码中不再出现 HOTEL-TEST 作为业务兜底酒店。
  • 文档、测试用例、SuperAgent MCP tools schema 与真实能力一致。