# TH Hotel 上线注意事项 本文给产品、研发、测试、运维和后续协作 agent 使用,目标是把上线前必须确认的事项放在同一个地方。这里记录的是当前项目专属要求,不作为可整份复制到其他项目的通用模板。 ## 1. 当前上线范围 当前后端已经具备以下能力: - `GET /api/health`:后端健康检查。 - `GET /api/source-messages`:查询 SourceMessage Inbox 安全摘要。 - `GET /api/source-messages/{id}`:查询单条 SourceMessage 安全摘要。 - `GET /api/source-messages/{id}/original`:受控读取邮件原文、HTML 和媒体 URL,并记录访问审计。 - `GET /api/system/agentbus-probe`:查看 AgentBus WebSocket 连接状态和安全计数器。 - AgentBus WebSocket 入站链路:默认关闭,开启后只把业务 frame 写入 SourceMessage Inbox。 - SuperAgent 任务结果接收接口:接收一个外部 `source_message_id` 下的 AI 任务结果,反查 SourceMessage Inbox 后写入 AI 过渡层、订单、任务和任务卡。 - Reservation 任务详情接口:返回任务字段、队列可处理状态和 OPERA 模拟操作摘要。 - Reservation 任务草稿保存和最终确认接口:按任务卡矩阵做第一版后端校验,确认后生成 `confirmed_payload_json`。 - Reservation OPERA 模拟骨架:已确认任务固定生成两条模拟操作,支持执行、失败重试、attempt 记录和任务审计列表。 - SuperAgent 查询上下文接口 1、2:支持 HMAC 鉴权的订单上下文查询和对象详情查询。 - Debug EML 上传到 SuperAgent 调试链路:受控上传 `.eml`、转存阿里云 OSS、写入 SourceMessage Inbox、调用 SuperAgent Open API 并返回调试结果。 当前不要把以下能力当作已上线: - SourceMessage Replay 到 MessageEvent / Evidence。 - 真实 AI 识别服务实现、Case 完整模型、Operation、Receipt。 - 自动 ACK、`task.result` 或客户回复。 - 业务前端页面展示邮件原文。 - OHIP / OPERA 或其他业务系统真实写操作。 - SuperAgent 查询上下文接口 3、4。 - 普通任务切换订单接口。 - 用户身份、权限和真实审计 actor。 - Debug EML 上传链路不属于生产普通业务页面能力,生产默认关闭;未接入正式用户权限前不要开放给普通用户。 ## 2. 上线前必须确认 上线前至少确认以下事项: - 当前分支、提交和部署包来源清楚,不能混入本地临时文件、真实 Secret、真实客户邮件样本或构建产物。 - `server` 后端通过完整检查:`cd server && ./mvnw verify`。 - 生产或 UAT 数据库已经备份,并确认 Flyway migration 只新增不修改历史脚本。 - 所有 Secret 都通过环境变量、部署平台 Secret 或密钥管理系统注入,不写入仓库、镜像、前端环境变量或普通配置文件。 - 生产默认不保存 AgentBus raw frame 样本。 - AgentBus 实时链路开启前,已经确认 WebSocket URL、Token、Bot Address、外部消息幂等键和断线重连语义。 - 原文读取接口开启前,已经确认谁可以使用、在哪些场景使用、如何轮换访问 key。 - 日志采集、错误响应和监控面板都不会展示邮件正文、HTML、附件 URL、Token、Cookie、客户姓名、邮箱、电话或支付信息。 ## 3. 环境变量 ### 3.1 数据库 | 变量 | 是否 Secret | 上线注意事项 | | --- | --- | --- | | `TH_HOTEL_DB_URL` | 否 | 指向目标环境数据库。URL 包含 `&` 时要加引号。 | | `TH_HOTEL_DB_USERNAME` | 是 | 使用最小权限账号,不使用个人账号。 | | `TH_HOTEL_DB_PASSWORD` | 是 | 只能通过 Secret 注入,不写入仓库。 | | `TH_HOTEL_DB_DRIVER` | 否 | MySQL 使用 `com.mysql.cj.jdbc.Driver`。 | ### 3.2 SourceMessage | 变量 | 是否 Secret | 上线注意事项 | | --- | --- | --- | | `SOURCE_MESSAGE_DEV_ORIGINAL_READ_ACCESS_KEY` | 是 | dev 原文读取临时访问 key。未配置时可兜底使用旧通用变量。 | | `SOURCE_MESSAGE_TEST_ORIGINAL_READ_ACCESS_KEY` | 是 | test 原文读取临时访问 key。未配置时可兜底使用旧通用变量。 | | `SOURCE_MESSAGE_PROD_ORIGINAL_READ_ACCESS_KEY` | 是 | prod 原文读取临时访问 key。未配置时原文读取默认关闭。 | | `SOURCE_MESSAGE_ORIGINAL_READ_ACCESS_KEY` | 是 | 旧通用原文读取 key,仅作为兼容兜底。 | 注意: - 原文读取 key 不是用户体系,后续接入正式登录和角色权限后应替换。 - 任何能读取原文的调用都必须有调用方和访问场景,并写入审计表。 ### 3.3 AgentBus | 变量 | 是否 Secret | 上线注意事项 | | --- | --- | --- | | `AGENTBUS_PROBE_ENABLED` | 否 | 是否启用 WebSocket 长连接。生产首次上线建议先保持 `false`,完成连通性窗口后再打开。 | | `AGENTBUS_WS_URL` | 否 | AgentBus WebSocket 地址。 | | `AGENTBUS_WS_TOKEN` | 是 | WebSocket 鉴权 Token,只能通过 Secret 注入。 | | `AGENTBUS_WS_RECONNECT_DELAY` | 否 | 断线重连间隔,默认 `5s`。 | | `AGENTBUS_CONNECT_TIMEOUT` | 否 | 连接超时,默认 `15s`。 | | `AGENTBUS_MAX_FRAME_BYTES` | 否 | 单个入站 frame 最大字节数,默认 `1048576`。 | | `AGENTBUS_CAPTURE_ENABLED` | 否 | 是否把业务 frame 写入 SourceMessage Inbox。 | | `AGENTBUS_DEFAULT_HOTEL_ID` | 否 | AgentBus 未提供酒店上下文时的默认业务上下文。 | 注意: - `AGENTBUS_PROBE_ENABLED=true` 只表示启用连接和入站接收,不代表可以回复客户。 - `AGENTBUS_CAPTURE_ENABLED=false` 时,业务 frame 会被忽略,不会写入 SourceMessage Inbox。 - 当前实现不发送 ACK、不发送 `task.result`、不自动回复客户。 ### 3.4 SuperAgent HMAC | 变量 | 是否 Secret | 上线注意事项 | | --- | --- | --- | | `SUPERAGENT_DEV_TASK_RESULT_HMAC_SECRET` | 是 | dev SuperAgent 查询接口 1、2 和任务结果通知接口共用的 HMAC secret。 | | `SUPERAGENT_TEST_TASK_RESULT_HMAC_SECRET` | 是 | test SuperAgent 查询接口 1、2 和任务结果通知接口共用的 HMAC secret。 | | `SUPERAGENT_PROD_TASK_RESULT_HMAC_SECRET` | 是 | prod SuperAgent 查询接口 1、2 和任务结果通知接口共用的 HMAC secret。生产不能为空,只能通过 Secret 注入。 | | `SUPERAGENT_TASK_RESULT_HMAC_SECRET` | 是 | 旧通用 HMAC 变量,仅作为兼容兜底;新环境优先配置环境专属变量。 | | `SUPERAGENT_TASK_RESULT_CLOCK_SKEW_SECONDS` | 否 | 请求时间允许偏移,默认 `300` 秒。上线前确认本系统、SuperAgent 和 AgentBus 所在机器时间已通过 NTP 同步。 | | `SUPERAGENT_TASK_RESULT_NONCE_TTL_SECONDS` | 否 | nonce 防重放窗口,默认 `600` 秒。 | | `SUPERAGENT_TASK_RESULT_MAX_BODY_BYTES` | 否 | SuperAgent 入站请求体最大字节数,默认 `1048576`。 | 注意: - 查询接口和任务结果通知接口使用同一套 Header、签名串、secret、timestamp 和 nonce 规则。 - SuperAgent 侧也需要配置同一个 secret,并按原始请求体计算 SHA-256。 - 当前第一版只支持一个 HMAC secret,secret 轮换需要协调部署窗口。 - 任务结果通知接口里的 `source_message_id` 是外部来源消息 ID,对应 AgentBus `source.external_message_id`;正式请求必须带 `hotel_id`,后端用 `hotel_id + provider + channel + external_message_id` 反查内部 SourceMessage Inbox。 - SuperAgent 查询上下文接口中的 `source_message_id`、`source_event_index` 第一版仅兼容接收,不参与查询和校验;不要依赖它们限制查询范围。 ### 3.5 Debug EML / SuperAgent Open API / 阿里云 OSS | 变量 | 是否 Secret | 上线注意事项 | | --- | --- | --- | | `DEBUG_EML_UPLOAD_DEV_ENABLED` | 否 | dev 是否启用 Debug EML 上传接口,未配置时可兜底 `DEBUG_EML_UPLOAD_ENABLED`。 | | `DEBUG_EML_UPLOAD_TEST_ENABLED` | 否 | test 是否启用 Debug EML 上传接口,默认关闭。 | | `DEBUG_EML_UPLOAD_PROD_ENABLED` | 否 | prod 默认必须保持 `false`;未接入正式用户权限前不要开放。 | | `DEBUG_EML_UPLOAD_DEV_ACCESS_KEY` | 是 | dev Debug EML 上传访问口令,未配置时可兜底 `DEBUG_EML_UPLOAD_ACCESS_KEY`。 | | `DEBUG_EML_UPLOAD_TEST_ACCESS_KEY` | 是 | test Debug EML 上传访问口令,未配置时可兜底 `DEBUG_EML_UPLOAD_ACCESS_KEY`。 | | `DEBUG_EML_UPLOAD_PROD_ACCESS_KEY` | 是 | prod Debug EML 上传访问口令;生产通常不应启用该接口。 | | `DEBUG_EML_UPLOAD_MAX_FILE_BYTES` | 否 | `.eml` 上传大小上限,默认 `10485760`。 | | `DEERFLOW_DEV_BASE_URL` / `DEERFLOW_TEST_BASE_URL` / `DEERFLOW_PROD_BASE_URL` | 否 | SuperAgent / DeerFlow Open API 基础地址,未配置时可兜底 `DEERFLOW_BASE_URL`。 | | `DEERFLOW_DEV_OPEN_API_KEY` / `DEERFLOW_TEST_OPEN_API_KEY` / `DEERFLOW_PROD_OPEN_API_KEY` | 是 | SuperAgent Open API Key,未配置时可兜底 `DEERFLOW_OPEN_API_KEY`。 | | `SUPERAGENT_DEV_OPEN_API_ENABLED` / `SUPERAGENT_TEST_OPEN_API_ENABLED` / `SUPERAGENT_PROD_OPEN_API_ENABLED` | 否 | 是否启用真实 SuperAgent Open API 调用;prod 默认关闭。 | | `SUPERAGENT_DEBUG_EML_EXTERNAL_SUBJECT_ID` | 否 | Debug EML 创建 SuperAgent session 的 external subject id。 | | `SUPERAGENT_DEBUG_EML_CONNECT_TIMEOUT` | 否 | SuperAgent Open API 建连超时,默认 `15s`。 | | `SUPERAGENT_DEBUG_EML_READ_TIMEOUT` | 否 | SuperAgent SSE 读取超时,默认 `180s`。 | | `ALIYUN_OSS_DEV_ENDPOINT` / `ALIYUN_OSS_TEST_ENDPOINT` / `ALIYUN_OSS_PROD_ENDPOINT` | 否 | 阿里云 OSS Endpoint,未配置时可兜底 `ALIYUN_OSS_ENDPOINT`。 | | `ALIYUN_OSS_DEV_BUCKET` / `ALIYUN_OSS_TEST_BUCKET` / `ALIYUN_OSS_PROD_BUCKET` | 否 | 阿里云 OSS Bucket,未配置时可兜底 `ALIYUN_OSS_BUCKET`。 | | `ALIYUN_OSS_DEV_ACCESS_KEY_ID` / `ALIYUN_OSS_TEST_ACCESS_KEY_ID` / `ALIYUN_OSS_PROD_ACCESS_KEY_ID` | 是 | 阿里云 OSS AccessKey ID,未配置时可兜底 `ALIYUN_OSS_ACCESS_KEY_ID`。 | | `ALIYUN_OSS_DEV_ACCESS_KEY_SECRET` / `ALIYUN_OSS_TEST_ACCESS_KEY_SECRET` / `ALIYUN_OSS_PROD_ACCESS_KEY_SECRET` | 是 | 阿里云 OSS AccessKey Secret,未配置时可兜底 `ALIYUN_OSS_ACCESS_KEY_SECRET`。 | | `ALIYUN_OSS_DEV_PUBLIC_BASE_URL` / `ALIYUN_OSS_TEST_PUBLIC_BASE_URL` / `ALIYUN_OSS_PROD_PUBLIC_BASE_URL` | 否 | OSS 对外访问基础 URL,未配置时可兜底 `ALIYUN_OSS_PUBLIC_BASE_URL`。 | | `ALIYUN_OSS_DEBUG_EML_PREFIX` | 否 | Debug EML 上传对象路径前缀,默认 `debug/eml/`。 | 注意: - Debug EML 上传接口会接收原始邮件、上传 OSS 并调用 SuperAgent,风险和成本高于普通查询接口。 - Debug EML 上传 key、SuperAgent Open API Key、阿里云 OSS AccessKey 都不得进入前端源码、`VITE_*`、镜像、普通日志或文档真实值。 - Debug EML 写入 SourceMessage Inbox 时 `provider=DEBUG_EML_UPLOAD`,不能伪装为 AgentBus 来源。 - Debug EML 写入 SourceMessage Inbox 时 `external_message_id` 是后端生成的 `debug-eml-run-{debugRunId}-{sha256前缀}`;原始邮件 `Message-ID` 只保存在 payload 的 `source.original_message_id`。 - Debug EML 返回 `html_body_sanitized`、`html_sanitize_required`、`html_render_mode`,前端展示 HTML 时应优先使用清洗字段。 - Debug EML 第一版只展示 SuperAgent 结果,不创建订单、不创建任务、不调用任务结果通知接口。 - AgentBus 实时收到邮件后自动推 SuperAgent 当前未实现,不能把 Debug EML 链路等同于生产实时自动处理链路。 ## 4. 数据库上线注意事项 当前 SourceMessage 相关 migration: - `server/src/main/resources/db/migration/V1__create_source_message_inbox.sql` - `server/src/main/resources/db/migration/V2__create_source_message_original_access_audit.sql` 当前 M002 订单任务相关 migration: - `server/src/main/resources/db/migration/V3__create_reservation_ai_task_workflow.sql` - `server/src/main/resources/db/migration/V4__harden_reservation_task_queue_and_manual_conversion.sql` - `server/src/main/resources/db/migration/V5__add_reservation_task_draft_and_confirmation.sql` - `server/src/main/resources/db/migration/V6__create_reservation_opera_simulation_tables.sql` 当前 M004 Debug EML 相关 migration: - `server/src/main/resources/db/migration/V7__create_debug_eml_superagent_run.sql` 上线前确认: - 目标数据库为空库或 Flyway history 与当前代码一致。 - MySQL 版本满足项目要求,默认使用 MySQL 8.0+。 - migration 在 UAT 或测试库已经跑过。 - 表和字段中文注释能正常创建。 - 数据库业务时间点按 UTC 写入,接口层返回带 `Z` 的 ISO 8601 UTC 时间,例如 `2026-07-08T03:00:00Z`。 - MySQL JDBC URL 建议明确 `serverTimezone=UTC`;部署容器和 JVM 也应使用 UTC,或至少确认应用代码所有入库时间均通过 UTC 时钟生成。 - AgentBus 邮件来源时间、SuperAgent HMAC timestamp、本系统 `created_at` / `updated_at` / `received_at` 等时间点统一按 UTC 理解;前端展示时再按用户或酒店时区格式化。 - 入住日期、离店日期、酒店营业日属于酒店本地业务日期,不应因为 UTC 换算而自动前后偏移。 - 执行 V4 前,如果目标库已有 M002 试运行数据,必须先检查 ACTIVE 订单业务号重复和同订单任务队列序号重复。 - 执行 V5 / V6 前,如果目标库已有 M002 试运行数据,必须确认任务草稿、确认 payload 和 OPERA 模拟操作表允许从空数据开始补齐;不要手工伪造已确认 payload 或 attempt 历史。 V4 前置检查 SQL: ```sql SELECT hotel_id, order_key_type, order_business_key, COUNT(*) AS duplicate_count FROM workflow_reservation_order WHERE order_status = 'ACTIVE' AND order_key_type IN ('GROUP_CODE', 'CONFIRMATION_NUMBER') AND order_business_key IS NOT NULL GROUP BY hotel_id, order_key_type, order_business_key HAVING COUNT(*) > 1; SELECT hotel_id, order_id, queue_participation, execution_order, COUNT(*) AS duplicate_count FROM workflow_reservation_task GROUP BY hotel_id, order_id, queue_participation, execution_order HAVING COUNT(*) > 1; ``` 处理要求: - 如果任一 SQL 返回记录,不要继续执行 V4。 - ACTIVE 订单业务号重复时,先由业务确认保留哪一条 ACTIVE,其他订单应转为 `LOGIC_DELETED`、`ENDED` 或完成任务迁移后再上线。 - 同订单任务队列序号重复时,先按来源顺序和审计证据重新分配 `execution_order`,确认前置任务关系正确后再上线。 - 不要为了让唯一索引创建成功而随意删除订单、任务或 AI 原始记录。 - OPERA 模拟操作失败只代表单条模拟操作失败,当前任务不会因此自动进入 `FAILED`;上线验证时不能把失败操作当作真实 OPERA 失败处理。 禁止事项: - 禁止直接修改已发布 migration。 - 禁止手工改生产表结构后再让代码“凑合跑”。 - 禁止把真实邮件正文、附件 URL 或客户数据做成测试种子数据提交。 ## 5. 安全与日志 上线前必须确认普通日志、错误响应和状态接口不会输出: - Authorization、Cookie、CSRF Token。 - Provider API Key、AgentBus Token、数据库密码。 - 邮件正文、HTML、附件 URL、原始 payload。 - 客户姓名、完整邮箱、电话、证件号。 - 支付信息。 SourceMessage 普通列表和普通详情只能返回安全摘要: - 可以返回:SourceMessage ID、酒店 ID、provider、channel、外部邮件 ID、外部邮件链 ID、状态、时间、发送人摘要、主题摘要、安全短摘要。 - 不应返回:完整正文、HTML、附件 URL、原始 payload。 原文读取接口只允许在明确授权场景下使用: ```text GET /api/source-messages/{id}/original Header: X-TH-Hotel-Source-Original-Read-Key Header: X-TH-Hotel-Actor Header: X-TH-Hotel-Access-Scene ``` 前端展示 `htmlBody` 前必须 sanitize。后端返回 `htmlSanitizeRequired=true` 是提醒前端不要直接信任 HTML。 ## 6. AgentBus 上线注意事项 AgentBus 是消息入口,不是 AI Provider。生产实时链路必须保持以下边界: - 只写 SourceMessage Inbox。 - 不自动调用 SuperAgent。 - 不创建 MessageEvent、Evidence、Case、Task、Operation、Receipt。 - 不调用 OHIP、ERP、支付系统等业务写接口。 - 不自动发送 ACK、`task.result` 或客户回复。 - 不在普通日志里输出 raw frame、邮件正文、HTML 或附件 URL。 建议上线顺序: 1. 保持 `AGENTBUS_PROBE_ENABLED=false`,先部署服务并确认健康检查。 2. 确认数据库 migration 和 SourceMessage 查询接口正常。 3. 配置 AgentBus URL 和 Token,但仍保持连接关闭。 4. 在约定观察窗口打开 `AGENTBUS_PROBE_ENABLED=true`。 5. 观察 `/api/system/agentbus-probe`,确认连接状态、`sessionReady`、计数器和最近错误代码。 6. 用合成测试邮件验证 SourceMessage Inbox 是否写入。 7. 确认日志和监控没有泄露 raw frame、正文或附件 URL。 如果出现异常: - 先关闭 `AGENTBUS_PROBE_ENABLED`,停止接收入站 frame。 - 如果只是想暂停入库但保留连接,可关闭 `AGENTBUS_CAPTURE_ENABLED`。 - 保留状态接口、应用日志和数据库记录用于排查,但不要导出真实邮件正文或附件 URL。 ## 7. 上线后冒烟验证 后端服务启动后,按顺序验证: ```text GET /api/health ``` 期望: - HTTP 200。 - `status = UP`。 ```text GET /api/system/agentbus-probe ``` 期望: - HTTP 200。 - 返回 `enabled`、`connected`、`sessionReady`、计数器和最近错误代码。 - 响应中不包含 Token、Authorization、raw frame、payload 或邮件正文。 ```text GET /api/source-messages?pageNum=1&pageSize=20 ``` 期望: - HTTP 200。 - 只返回安全摘要。 - 不包含正文、HTML、附件 URL 或原始 payload。 ```text GET /api/source-messages/{id} ``` 期望: - HTTP 200 或 404。 - 如果存在记录,只返回安全摘要。 ```text GET /api/source-messages/{id}/original ``` 期望: - 未携带正确访问 key 时返回 403。 - 携带正确访问 key、调用方和访问场景时返回原文内容,并写入 `platform_source_message_original_access_audit`。 ## 8. 监控建议 至少监控: - 应用进程是否存活。 - `GET /api/health` 是否正常。 - AgentBus `connected` 和 `sessionReady` 状态。 - AgentBus `failedFrameCount`、`rejectedFrameCount` 是否持续增长。 - SourceMessage Inbox 每小时入库数量是否异常突增或归零。 - `FAILED` SourceMessage 数量和安全错误摘要。 - 原文读取审计数量是否异常。 - 数据库连接池、慢 SQL、磁盘空间和 migration 状态。 告警信息不得包含 Secret、正文、HTML、附件 URL 或客户个人信息。 ## 9. 回滚与降级 优先降级开关: ```text AGENTBUS_PROBE_ENABLED=false AGENTBUS_CAPTURE_ENABLED=false SOURCE_MESSAGE_PROD_ORIGINAL_READ_ACCESS_KEY= SOURCE_MESSAGE_ORIGINAL_READ_ACCESS_KEY= ``` 说明: - 关闭 `AGENTBUS_PROBE_ENABLED` 可以停止 WebSocket 入站连接。 - 关闭 `AGENTBUS_CAPTURE_ENABLED` 可以保留连接但暂停写入 Inbox。 - 清空当前 profile 对应的 `SOURCE_MESSAGE_*_ORIGINAL_READ_ACCESS_KEY`,且不配置旧通用变量,可以关闭原文读取接口。 数据库回滚注意: - 已执行的 migration 不应直接删除或手工回滚。 - 如果新版本已写入 SourceMessage 数据,回滚应用前要确认旧版本是否能兼容新表存在。 - 需要修复表结构时,应新增 migration,而不是修改已发布 migration。 ## 10. 上线责任确认 上线前需要有人明确确认: - 部署版本:确认本次上线的分支、提交和构建产物。 - 数据库:确认 migration、备份和连接信息。 - Secret:确认所有密钥由部署平台注入。 - AgentBus:确认是否开启 WebSocket,是否允许捕获入库。 - 安全:确认日志、错误响应、状态接口和监控面板没有敏感数据。 - 业务:确认当前上线范围不包含 replay、AI、Case、Task、客户回复或 OHIP 写操作。 只要上述任何一项没人确认,就不要打开生产实时入口。