Files
th-hotel-simple/docs/project/requirements/M003-identity-access-hotel-menu-v1.md
2026-07-10 18:32:34 +08:00

669 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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>`
- 前端第一版将 `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
<module>
├── 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 |
| `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_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 <access_token>
```
行为:
- 将当前 session 标记为 `REVOKED`
- 重复登出可以幂等返回成功。
### 8.3 当前用户
```text
GET /api/auth/me
Authorization: Bearer <access_token>
```
返回:
```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. 查询类接口先要求登录和酒店权限。
2. 写操作再要求具体操作权限。
3. 邮件原文读取迁移到 `SOURCE_MESSAGE_ORIGINAL_READ`
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_ORIGINAL_READ` 权限 |
第一期不强制改完所有业务审计 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` | 否 | 初始默认酒店 IDdev/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_ORIGINAL_READ`
- 审计 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运行测试中文提交。
```