# M003 Identity Access Hotel Menu 登录权限与酒店菜单底座 V1 ## 文档信息 | 项目 | 内容 | | --- | --- | | 文档版本 | 0.2 | | 日期 | 2026-07-09 | | 状态 | CP1 后端第一版已实现,管理后台和前端登录页待做 | | 适用范围 | 本系统用户登录、角色权限、菜单、酒店授权和当前用户上下文底座 | | 主要读者 | 产品、后端、前端、测试、后续协作 agent | ## 1. 文档定位 本文记录 M003 登录权限与酒店菜单底座的第一版设计。该模块是平台通用能力,不属于 `workflows.reservation`,后续 Reservation、SourceMessage、审计、OPERA 执行等业务能力 都应通过该底座获取当前用户、权限和酒店上下文。 第一期目标是先让系统具备登录态、当前用户上下文、权限码、可访问酒店和可见菜单能力; 管理后台 CRUD 暂不实现,但必须在数据模型和接口边界上为后续扩展留好位置。 ## 2. 已确认决策 - 第一版采用用户名密码登录,本系统自己管理用户、角色、权限、菜单和酒店。 - 登录态采用数据库 session token,不使用 JWT。 - 登录成功后后端生成随机 `access_token`,数据库只保存 `token_hash`。 - 前端请求使用 `Authorization: Bearer `。 - 前端第一版将 `access_token` 保存在 `sessionStorage`,刷新同一浏览器会话可恢复,关闭浏览器后需要重新登录;不得放入 `localStorage`、URL 或普通日志。 - session 默认有效期第一版为 12 小时,即 `AUTH_SESSION_TTL_MINUTES=720`,可按环境覆盖。 - 用户和酒店关系采用第三种模型: - 管理员可访问全部酒店。 - 普通用户绑定一个或多个酒店。 - 普通用户有默认酒店,可在授权酒店之间切换。 - 当前前端已有菜单入口为订单列表、任务队列和 Debug EML;订单详情、任务详情和邮件会话属于隐藏详情路由,不作为菜单返回。 - 初始超级管理员通过环境变量初始化: - `AUTH_BOOTSTRAP_ADMIN_USERNAME` - `AUTH_BOOTSTRAP_ADMIN_PASSWORD` - `AUTH_BOOTSTRAP_ADMIN_DISPLAY_NAME` - 系统中已存在启用状态的超级管理员后,不再使用环境变量覆盖管理员账号或密码;如果只存在禁用的超级管理员,环境变量仍可初始化一个可登录超级管理员,避免系统锁死。 - 第一期开启登录和权限底座,但不强制拦截现有业务接口。 - 系统管理后台已由 M006 承接;M003 仍只描述登录、权限、酒店和菜单运行时底座。 ## 3. 核心目标 第一期要解决以下问题: - 前端可以登录、登出,并获取当前用户信息。 - 前端可以拿到当前用户可访问酒店、默认酒店和可见菜单。 - 后端可以从 token 解析当前用户上下文。 - 后端可以判断当前用户是否拥有某个权限码。 - 后端可以判断当前用户是否可访问某个 `hotel_id`。 - 审计 actor 后续可以从当前用户上下文读取,不再依赖本地占位值。 - SourceMessage 原文读取等高敏能力后续可以从临时 access-key 逐步迁移到权限码。 ## 4. 非目标范围 第一期不做以下能力: - 用户新增、编辑、禁用、重置密码接口。 - 角色新增、编辑、删除接口。 - 权限分配接口。 - 菜单新增、编辑、排序接口。 - 酒店新增、编辑、停用接口。 - 用户绑定酒店管理接口。 - 管理后台页面。 - 强制要求现有 Reservation / SourceMessage 业务接口必须带 token。 - 外部 SSO、OIDC、企业微信、LDAP 或网关注入用户。 - JWT refresh token 体系。 - 密码找回、短信验证码、MFA、多端设备管理。 这些能力后续作为管理后台和安全增强 checkpoint 独立设计。 ## 5. 模块边界 后端建议拆成平台模块: ```text platform.identity // 用户、登录、session、当前用户上下文 platform.access // 角色、权限、用户角色、权限判断 platform.navigation // 菜单树、菜单权限、当前用户可见菜单 platform.hotel // 酒店基础信息、用户可访问酒店、默认酒店 platform.security // 请求过滤器、Bearer token 解析、可选鉴权上下文 ``` 中文说明: | 模块 | 职责 | 不负责 | | --- | --- | --- | | `platform.identity` | 用户账号、登录、登出、session token、当前用户 | 不维护业务订单和任务 | | `platform.access` | 角色、权限码、用户角色、权限判断 | 不直接决定菜单 UI 样式 | | `platform.navigation` | 菜单树、菜单权限、用户可见菜单 | 不做业务接口权限校验 | | `platform.hotel` | 酒店基础资料、用户酒店授权、默认酒店 | 不保存订单、任务或邮件正文 | | `platform.security` | 请求级 token 解析、当前用户上下文注入 | 不替代 SuperAgent / AgentBus 服务间鉴权 | 目录仍遵守当前后端规范: ```text ├── control ├── service │ └── impl ├── domain ├── mapper ├── repository └── common ├── dto ├── request ├── result └── enums ``` ## 6. 数据模型建议 ### 6.1 用户表 `platform_user` | 字段 | 中文说明 | | --- | --- | | `id` | 用户内部 ID | | `username` | 登录用户名,全局唯一 | | `password_hash` | 密码哈希,禁止保存明文密码 | | `display_name` | 用户展示名称 | | `email` | 邮箱,可为空 | | `phone` | 手机号,可为空 | | `user_status` | 用户状态,例如 `ACTIVE`、`DISABLED` | | `super_admin` | 是否超级管理员 | | `password_changed_at` | 最近密码变更时间 | | `last_login_at` | 最近登录时间 | | `created_at` / `updated_at` | 创建和更新时间 | 密码哈希建议使用 BCrypt 或同等级单向哈希算法。日志、错误响应和审计不得输出密码、密码哈希或 token。 ### 6.2 Session 表 `platform_user_session` | 字段 | 中文说明 | | --- | --- | | `id` | session 内部 ID | | `user_id` | 所属用户 ID | | `token_hash` | access token 哈希,数据库不保存明文 token | | `session_status` | session 状态,例如 `ACTIVE`、`REVOKED`、`EXPIRED` | | `issued_at` | 签发时间 | | `expires_at` | 过期时间 | | `revoked_at` | 注销或撤销时间 | | `last_seen_at` | 最近使用时间 | | `client_ip` | 登录或最近请求 IP,第一版可为空 | | `user_agent_summary` | User-Agent 摘要,第一版可为空 | 第一版 session 过期时间通过配置控制,例如 `AUTH_SESSION_TTL_MINUTES`,默认使用 720 分钟。 ### 6.3 角色表 `platform_role` | 字段 | 中文说明 | | --- | --- | | `id` | 角色内部 ID | | `role_code` | 稳定角色代码,例如 `SYSTEM_ADMIN`、`RESERVATION_OPERATOR` | | `role_name` | 角色展示名称 | | `role_status` | 角色状态 | | `system_builtin` | 是否系统内置角色 | | `created_at` / `updated_at` | 创建和更新时间 | ### 6.4 权限表 `platform_permission` | 字段 | 中文说明 | | --- | --- | | `id` | 权限内部 ID | | `permission_code` | 稳定权限代码 | | `permission_name` | 权限展示名称 | | `permission_group` | 权限分组,例如 `RESERVATION`、`SOURCE_MESSAGE`、`SYSTEM` | | `permission_status` | 权限状态 | | `system_builtin` | 是否系统内置权限 | 权限码只能使用稳定英文代码,不使用中文或菜单文案做业务判断。 ### 6.5 用户角色表 `platform_user_role` | 字段 | 中文说明 | | --- | --- | | `id` | 关系内部 ID | | `user_id` | 用户 ID | | `role_id` | 角色 ID | | `created_at` | 创建时间 | ### 6.6 角色权限表 `platform_role_permission` | 字段 | 中文说明 | | --- | --- | | `id` | 关系内部 ID | | `role_id` | 角色 ID | | `permission_id` | 权限 ID | | `created_at` | 创建时间 | ### 6.7 酒店表 `platform_hotel` | 字段 | 中文说明 | | --- | --- | | `id` | 酒店内部 ID | | `hotel_id` | 业务酒店 ID,例如 `HOTEL-TEST` | | `hotel_name` | 酒店展示名称 | | `hotel_status` | 酒店状态,例如 `ACTIVE`、`DISABLED` | | `time_zone` | 酒店本地时区,用于入住日期、离店日期等酒店本地业务日期 | | `sort_order` | 排序号 | | `created_at` / `updated_at` | 创建和更新时间 | 现有业务表中的 `hotel_id` 继续作为业务上下文 ID。用户酒店权限必须围绕该字段校验。 ### 6.8 用户酒店授权表 `platform_user_hotel` | 字段 | 中文说明 | | --- | --- | | `id` | 关系内部 ID | | `user_id` | 用户 ID | | `hotel_id` | 酒店业务 ID | | `default_hotel` | 是否该用户默认酒店 | | `created_at` | 创建时间 | 规则: - 普通用户至少应有一个可访问酒店。 - 普通用户最多只能有一个默认酒店。 - 后端写入用户酒店授权时,如果将某个酒店设为默认酒店,会同时清理该用户其他酒店默认标记;数据库通过生成列和唯一索引兜底约束同一用户最多一条 `default_hotel=1`。 - 超级管理员不需要为每个酒店插授权关系,默认可访问全部启用酒店。 - 业务接口收到 `hotel_id` 时,后续强制鉴权阶段必须校验当前用户是否可访问该酒店。 ### 6.9 菜单表 `platform_menu` | 字段 | 中文说明 | | --- | --- | | `id` | 菜单内部 ID | | `parent_id` | 父菜单 ID,根菜单为空 | | `menu_code` | 稳定菜单代码 | | `menu_name` | 菜单展示名称 | | `menu_type` | 菜单类型,例如 `GROUP`、`PAGE`、`ACTION` | | `route_path` | 前端路由路径 | | `component_key` | 前端组件标识,第一版可为空 | | `icon_key` | 前端图标标识 | | `permission_code` | 访问该菜单需要的权限码,可为空 | | `sort_order` | 菜单排序 | | `visible` | 是否可见 | | `menu_status` | 菜单状态 | 菜单权限只决定入口是否可见,不替代后端接口权限校验。 ## 7. 初始内置数据 第一期可内置以下角色: | 角色代码 | 中文说明 | | --- | --- | | `SYSTEM_ADMIN` | 系统管理员,可访问全部酒店和系统能力 | | `RESERVATION_OPERATOR` | 预订处理员,可处理 Reservation 任务 | | `RESERVATION_VIEWER` | 预订只读查看员,只能查看任务、订单和邮件摘要 | 第一期建议内置以下权限码: | 权限码 | 中文说明 | | --- | --- | | `SOURCE_MESSAGE_READ` | 查看来源消息安全摘要 | | `SOURCE_MESSAGE_ORIGINAL_READ` | 查看来源消息原文和媒体 URL;必须叠加 `SOURCE_MESSAGE_READ` 使用 | | `RESERVATION_ORDER_READ` | 查看订单 | | `RESERVATION_TASK_READ` | 查看任务 | | `RESERVATION_TASK_EDIT` | 保存任务草稿 | | `RESERVATION_TASK_CONFIRM` | 最终确认任务 | | `RESERVATION_OPERA_SIM_EXECUTE` | 执行或重试 OPERA 模拟 | | `RESERVATION_AUDIT_READ` | 查看任务审计 | | `HOTEL_SWITCH` | 在授权酒店之间切换 | | `SYSTEM_AUTH_READ` | 读取当前用户、菜单和权限信息 | | `SYSTEM_USER_MANAGE` | 用户管理,第一期不开放管理接口 | | `SYSTEM_ROLE_MANAGE` | 角色权限管理,第一期不开放管理接口 | | `SYSTEM_MENU_MANAGE` | 菜单管理,第一期不开放管理接口 | | `HOTEL_MANAGE` | 酒店管理,第一期不开放管理接口 | | `SYSTEM_DEBUG_EML_RUN` | 访问 Debug EML 上传到 SuperAgent 调试页面 | 第一期内置角色权限矩阵: | 角色代码 | 默认权限 | | --- | --- | | `SYSTEM_ADMIN` | 全部权限,包括当前第一期权限和后续管理类占位权限 | | `RESERVATION_OPERATOR` | `SYSTEM_AUTH_READ`、`HOTEL_SWITCH`、`RESERVATION_ORDER_READ`、`RESERVATION_TASK_READ`、`RESERVATION_TASK_EDIT`、`RESERVATION_TASK_CONFIRM`、`RESERVATION_OPERA_SIM_EXECUTE`、`RESERVATION_AUDIT_READ`、`SOURCE_MESSAGE_READ`、`SOURCE_MESSAGE_ORIGINAL_READ` | | `RESERVATION_VIEWER` | `SYSTEM_AUTH_READ`、`HOTEL_SWITCH`、`RESERVATION_ORDER_READ`、`RESERVATION_TASK_READ`、`RESERVATION_AUDIT_READ`、`SOURCE_MESSAGE_READ` | 中文说明: - `SYSTEM_ADMIN` 用于系统初始化和调试能力,第一版可访问所有启用酒店。 - `RESERVATION_OPERATOR` 需要查看邮件原文和附件外链来处理任务,因此第一版同时包含 `SOURCE_MESSAGE_READ` 和 `SOURCE_MESSAGE_ORIGINAL_READ`。 - `RESERVATION_VIEWER` 只读查看订单、任务、审计和来源消息安全摘要;不允许保存、确认、执行 OPERA 模拟,也不允许访问 Debug EML。 - `SYSTEM_DEBUG_EML_RUN` 第一版只授予 `SYSTEM_ADMIN`,避免普通业务用户触发 SuperAgent 调试链路。 - 启动初始化会按上表同步内置角色权限矩阵:矩阵中新增的权限会补齐,矩阵中移除的旧关系会清理。后续如果管理后台允许人工改内置角色,需要先重新确认“代码矩阵”和“后台配置”的优先级。 第一期可内置菜单,需和当前前端路由保持一致: | 菜单代码 | 路由 | 权限码 | 图标 Key | 可见性 | 中文说明 | | --- | --- | --- | --- | --- | --- | | `RESERVATION_ORDERS` | `/reservation/orders` | `RESERVATION_ORDER_READ` | `pi pi-list` | 可见 | 订单列表 | | `RESERVATION_TASKS` | `/reservation/tasks` | `RESERVATION_TASK_READ` | `pi pi-check-square` | 可见 | 任务队列 | | `DEBUG_EML_SUPERAGENT` | `/debug/eml-superagent` | `SYSTEM_DEBUG_EML_RUN` | `pi pi-upload` | 有权限时可见 | Debug EML 上传到 SuperAgent | | `SOURCE_MESSAGES` | `/source-messages` | `SOURCE_MESSAGE_READ` | `pi pi-envelope` | 第一版隐藏 | 来源消息独立页面,前端尚未实现 | | `SYSTEM_SETTINGS` | `/system` | `SYSTEM_USER_MANAGE` | `pi pi-cog` | 第一版隐藏 | 系统设置入口,占位为后续管理后台使用 | 当前前端隐藏详情路由不作为菜单返回: | 路由 | 中文说明 | | --- | --- | | `/reservation/orders/{orderId}` | 订单详情,通过订单列表或任务入口进入 | | `/reservation/tasks/{taskId}` | 任务详情,通过任务列表或订单时间线进入 | | `/reservation/source-messages/{sourceMessageId}/conversation` | 邮件会话详情,通过任务或订单详情来源入口进入 | 菜单数据应允许后续通过管理后台维护。第一版 `icon_key` 先兼容当前 PrimeIcons class;如果后续换成后端稳定图标代码,前端再统一做映射。 ## 8. 接口契约第一版 ### 8.1 登录 ```text POST /api/auth/login ``` 请求体: ```json { "username": "admin", "password": "password" } ``` 成功响应: ```json { "access_token": "plain-token-only-return-once", "token_type": "Bearer", "expires_at": "2026-07-09T12:00:00Z", "user": { "id": "1900000000000000001", "username": "admin", "display_name": "系统管理员", "super_admin": true }, "default_hotel_id": "HOTEL-TEST", "hotels": [ { "hotel_id": "HOTEL-TEST", "hotel_name": "测试酒店", "time_zone": "Asia/Bangkok", "default_hotel": true } ], "permissions": [ "SYSTEM_AUTH_READ", "RESERVATION_ORDER_READ", "RESERVATION_TASK_READ" ], "menus": [ { "menu_code": "RESERVATION_ORDERS", "menu_name": "订单列表", "route_path": "/reservation/orders", "component_key": "ReservationOrders", "icon_key": "pi pi-list", "permission_code": "RESERVATION_ORDER_READ", "sort_order": 10 }, { "menu_code": "RESERVATION_TASKS", "menu_name": "任务队列", "route_path": "/reservation/tasks", "component_key": "ReservationTasks", "icon_key": "pi pi-check-square", "permission_code": "RESERVATION_TASK_READ", "sort_order": 20 }, { "menu_code": "DEBUG_EML_SUPERAGENT", "menu_name": "Debug EML", "route_path": "/debug/eml-superagent", "component_key": "DebugEmlSuperAgent", "icon_key": "pi pi-upload", "permission_code": "SYSTEM_DEBUG_EML_RUN", "sort_order": 30 } ] } ``` 说明: - 登录失败统一返回用户名或密码错误,不暴露账号是否存在。 - 禁用用户不能登录。 - token 明文只在登录成功响应中返回一次。 - 数据库只保存 token hash。 - 前端第一版使用 `sessionStorage` 保存 token;退出登录时必须清理本地 token。 - 超级管理员如果拥有 `SYSTEM_DEBUG_EML_RUN`,`menus[]` 可包含 `/debug/eml-superagent`;普通预订角色不返回该菜单。 ### 8.2 登出 ```text POST /api/auth/logout Authorization: Bearer ``` 行为: - 将当前 session 标记为 `REVOKED`。 - 重复登出可以幂等返回成功。 ### 8.3 当前用户 ```text GET /api/auth/me Authorization: Bearer ``` 返回: ```json { "user": { "id": "1900000000000000001", "username": "admin", "display_name": "系统管理员", "super_admin": true }, "default_hotel_id": "HOTEL-TEST", "hotels": [ { "hotel_id": "HOTEL-TEST", "hotel_name": "测试酒店", "time_zone": "Asia/Bangkok", "default_hotel": true } ], "permissions": [ "RESERVATION_TASK_READ" ], "menus": [ { "menu_code": "RESERVATION_TASKS", "menu_name": "任务列表", "route_path": "/reservation/tasks", "component_key": "ReservationTasks", "icon_key": "pi pi-check-square", "permission_code": "RESERVATION_TASK_READ", "sort_order": 20 } ] } ``` 说明: - `GET /api/auth/me` 是前端启动后恢复登录态、菜单和酒店上下文的核心接口。 - 超级管理员的 `hotels` 可返回全部启用酒店。 - 普通用户只返回授权酒店。 ## 9. 现有业务接口接入策略 第一期不强制拦截现有业务接口。 行为规则: - `/api/auth/login` 不需要 token。 - `/api/auth/logout` 和 `/api/auth/me` 需要 token。 - 现有 Reservation / SourceMessage 查询和操作接口第一期继续保持未登录可访问,避免打断当前前后端联调。 - 如果现有业务接口请求带合法 token,后端可解析当前用户上下文。 - 如果现有业务接口未带 token,后端按匿名上下文处理,行为保持现状。 - 如果现有业务接口带了无效 token,第一期可对非强制鉴权接口忽略该 token,避免旧前端因为缓存脏 token 被整体打断。 - Debug EML 上传接口当前仍使用 `X-TH-Hotel-Debug-Upload-Key` 受控访问;M003 第一版只决定菜单是否可见,不把 debug access key 下发给前端。 - 后续强制鉴权 checkpoint 再逐步将业务接口改为必须登录和权限校验。 后续强制鉴权时应按接口分批启用: 1. 查询类接口先要求登录和酒店权限。已完成:Reservation / SourceMessage 第一批只读查询。 2. 写操作再要求具体操作权限。 3. 邮件原文读取迁移到 `SOURCE_MESSAGE_READ` + `SOURCE_MESSAGE_ORIGINAL_READ`。已完成:`/api/source-messages/{id}/original` 和 `/api/source-messages/{id}/conversation`。 4. OPERA 模拟迁移到 `RESERVATION_OPERA_SIM_EXECUTE`。 5. 审计查询迁移到 `RESERVATION_AUDIT_READ`。 ## 10. 酒店上下文规则 第一期登录态接入后,后端应提供统一的酒店上下文解析能力。 规则: - 请求显式传 `hotel_id` 时,后续强制鉴权阶段必须校验用户是否可访问该酒店。 - 请求未传 `hotel_id` 时,普通用户使用默认酒店。 - 超级管理员不传 `hotel_id` 时: - 管理后台可查询全部酒店。 - 业务页面建议仍使用默认酒店或要求前端选择酒店,避免误查全部数据。 - SuperAgent / AgentBus 服务间接口仍使用 HMAC / token 等服务间鉴权,不走人工用户登录态。 - SuperAgent / AgentBus 请求中的 `hotel_id` 后续应校验酒店是否存在并处于启用状态。 ## 11. 安全要求 - 密码不能明文保存。 - token 不能明文入库。 - token、密码和密码哈希不能出现在日志、错误响应、审计详情或前端配置。 - 登录失败不暴露账号是否存在。 - session 需要过期时间。 - 登出后 token 必须失效。 - 管理员初始化密码只能来自环境变量或部署 Secret。 - 前端不能保存任何服务端 Secret。 - 菜单隐藏不能替代后端权限校验。 ## 12. 审计和 actor 迁移 当前部分业务审计仍使用本地占位 actor。M003 第一期开启当前用户上下文后,后续业务模块应逐步迁移: | 场景 | 第一版迁移策略 | | --- | --- | | 用户保存任务草稿 | 有 token 时记录当前用户;无 token 时继续兼容本地占位 | | 用户最终确认任务 | 有 token 时记录当前用户;无 token 时继续兼容本地占位 | | Fallback 转换 | 有 token 时记录当前用户 | | OPERA 模拟执行 / 重试 | 有 token 时记录当前用户 | | 邮件原文读取 | 已从 access-key 迁移到 `SOURCE_MESSAGE_READ` + `SOURCE_MESSAGE_ORIGINAL_READ` 权限,并记录当前登录用户稳定 ID | 第一期不强制改完所有业务审计 actor,但需要提供可复用的当前用户上下文接口。 ## 13. 配置项建议 | 配置项 | 是否 Secret | 中文说明 | | --- | --- | --- | | `AUTH_BOOTSTRAP_ADMIN_USERNAME` | 否 | 初始超级管理员用户名 | | `AUTH_BOOTSTRAP_ADMIN_PASSWORD` | 是 | 初始超级管理员密码,只用于首次初始化 | | `AUTH_BOOTSTRAP_ADMIN_DISPLAY_NAME` | 否 | 初始超级管理员展示名 | | `AUTH_SESSION_TTL_MINUTES` | 否 | session 有效期,第一版默认 `720` 分钟 | | `AUTH_BOOTSTRAP_DEFAULT_HOTEL_ID` | 否 | 初始默认酒店 ID,dev/test 可使用 `HOTEL-TEST` 或当前环境约定值 | | `AUTH_BOOTSTRAP_DEFAULT_HOTEL_NAME` | 否 | 初始默认酒店名称 | | `AUTH_BOOTSTRAP_DEFAULT_HOTEL_TIME_ZONE` | 否 | 初始默认酒店时区 | 生产环境必须通过部署 Secret 或环境变量注入管理员初始密码,不得写入仓库、镜像或普通文档。 当前后端已按环境拆分配置: | 环境 | 推荐变量前缀 | 默认酒店 | | --- | --- | --- | | dev | `AUTH_DEV_*`,并兼容 `AUTH_*` 通用变量 | `HOTEL-DEV` / `开发酒店` | | test | `AUTH_TEST_*`,并兼容 `AUTH_*` 通用变量 | `HOTEL-TEST` / `测试酒店` | | prod | `AUTH_PROD_*`,并兼容 `AUTH_*` 通用变量 | 必须由生产 Secret 显式配置 | 例如 test 环境可配置: ```text AUTH_TEST_BOOTSTRAP_ADMIN_USERNAME=admin AUTH_TEST_BOOTSTRAP_ADMIN_PASSWORD=<测试环境初始密码> AUTH_TEST_BOOTSTRAP_ADMIN_DISPLAY_NAME=系统管理员 AUTH_TEST_BOOTSTRAP_DEFAULT_HOTEL_ID=HOTEL-TEST AUTH_TEST_BOOTSTRAP_DEFAULT_HOTEL_NAME=测试酒店 AUTH_TEST_BOOTSTRAP_DEFAULT_HOTEL_TIME_ZONE=Asia/Bangkok AUTH_TEST_SESSION_TTL_MINUTES=720 ``` 说明: - 启动初始化发现系统中已经存在启用状态的超级管理员后,不会继续用环境变量覆盖管理员用户名或密码;禁用状态超级管理员不阻止首次可登录管理员恢复初始化。 - 数据库 `platform_user_session` 只保存 `token_hash`,不保存明文 token。 - 当前后端提供可选 Bearer token 解析,现有 Reservation / SourceMessage 业务接口第一版仍不强制登录。 ## 14. Checkpoint 建议 ### CP1:登录和权限底座 状态:后端第一版已实现。 范围: - Flyway 创建用户、角色、权限、菜单、酒店和 session 表。 - 内置角色、权限、菜单基础数据。 - 支持环境变量初始化超级管理员和默认酒店。 - 实现登录、登出、当前用户接口。 - 实现数据库 session token。 - 实现可选 token 解析,不强制拦截现有业务接口。 - 提供当前用户上下文、权限判断和酒店授权判断服务。 不做: - 管理后台 CRUD。 - 强制拦截现有业务接口。 - 前端登录页面。 - 外部 SSO。 ### CP2:前端登录和菜单接入 范围: - 前端登录页。 - 使用 `sessionStorage` 保存 access token,登出或 token 失效时清理。 - 启动时调用 `/api/auth/me`。 - 根据 `menus[]` 渲染菜单,替换当前 `ReservationAppShell` 中订单列表、任务队列、Debug EML 的硬编码菜单。 - 根据 `hotels[]` 支持酒店选择或展示默认酒店。 ### CP3:业务接口逐步强制鉴权 范围: - Reservation 查询接口校验登录和酒店权限。 - Reservation 写接口校验具体权限。 - SourceMessage 原文读取迁移到 `SOURCE_MESSAGE_READ` + `SOURCE_MESSAGE_ORIGINAL_READ`。已完成 CP2,后续保持回归。 - 审计 actor 全面迁移到当前用户。 ### CP4:管理后台接口 范围: - 用户管理。 - 角色管理。 - 权限分配。 - 菜单管理。 - 酒店管理。 - 用户酒店授权。 该 checkpoint 还没有做,后续流程梳理或目标模式时必须提醒。 ## 15. 待确认问题 | 问题 | 建议 | 状态 | | --- | --- | --- | | session 默认有效期是多少? | 第一版使用 12 小时,即 `AUTH_SESSION_TTL_MINUTES=720`,可配置 | 已确认 | | 初始默认酒店在 dev/test/prod 的具体 ID 和名称是什么? | dev/test 先沿用现有 `HOTEL-DEV` / `HOTEL-TEST` 习惯,生产必须配置真实酒店 | 待确认 | | 前端菜单图标使用什么 icon key? | 第一版兼容当前 PrimeIcons class,例如 `pi pi-list`、`pi pi-check-square`、`pi pi-upload` | 已确认 | | 前端 token 保存在哪里? | 第一版使用 `sessionStorage`,不使用 `localStorage` | 已确认 | | Debug EML 菜单谁能看到? | 第一版仅 `SYSTEM_ADMIN` 通过 `SYSTEM_DEBUG_EML_RUN` 看到 | 已确认 | | 现有业务接口强制鉴权从哪一批开始? | M003 CP3 单独确认 | 后置 | | 管理后台什么时候做? | M003 CP4 单独确认 | 后置 | ## 16. 目标模式提示词建议 如果要进入目标模式开发 CP1,可以这样说: ```text 进入目标模式,目标:实现 M003 CP1 登录和权限底座。 范围: 1. 新增用户、角色、权限、菜单、酒店、用户酒店授权、session 的 Flyway 表结构。 2. 初始化内置权限、角色权限矩阵、菜单、默认酒店和超级管理员。 3. 实现用户名密码登录、登出、当前用户接口。 4. 登录态使用数据库 session token,数据库只保存 token_hash。 5. session 默认有效期 12 小时,支持 `AUTH_SESSION_TTL_MINUTES` 覆盖。 6. 初始化菜单需和当前前端路由对齐: - `RESERVATION_ORDERS` -> `/reservation/orders` - `RESERVATION_TASKS` -> `/reservation/tasks` - `DEBUG_EML_SUPERAGENT` -> `/debug/eml-superagent` - 订单详情、任务详情、邮件会话是隐藏路由,不作为菜单返回。 7. `SYSTEM_DEBUG_EML_RUN` 第一版只授予 `SYSTEM_ADMIN`。 8. 实现当前用户上下文、权限判断和酒店授权判断服务。 9. 第一版不强制拦截现有 Reservation / SourceMessage 业务接口;请求带合法 token 时可识别当前用户,未带 token 时保持现有行为。 10. Debug EML 上传接口仍使用 `X-TH-Hotel-Debug-Upload-Key`;M003 CP1 只控制菜单可见性,不下发 debug key。 11. 保持当前后端代码规范:control、service、service.impl、domain、mapper、repository、common.request、common.result、common.dto、common.enums。 12. Controller、Service、ServiceImpl 方法加中文注释;Entity 字段加中文注释。 13. 补充测试。 不做: 1. 用户 / 角色 / 权限 / 菜单 / 酒店管理后台 CRUD。 2. 前端登录页面。 3. 强制拦截现有业务接口。 4. 外部 SSO / JWT / MFA。 5. 真实 OPERA / OHIP。 完成后: 更新文档,code review,运行测试,中文提交。 ```