Files
th-hotel-simple/docs/project/backend-time-design.md
2026-07-10 23:49:49 +08:00

171 lines
6.7 KiB
Markdown
Raw 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.

# 后端时间设计说明
## 1. 文档定位
本文集中记录本项目的时间存储、接口返回和页面展示规则,避免把数据库里的 UTC 时间误当成酒店当地展示时间。
适用范围:
- 后端数据库时间字段设计。
- 后端 API 时间字段返回。
- 前端展示和按日期筛选。
- AgentBus / SuperAgent 等外部系统时间字段映射。
如本文与具体业务 PRD 冲突,以业务 PRD 的特殊说明为准;没有特殊说明时,统一遵守本文。
## 2. 总体原则
一句话原则:
数据库存 UTC 事实时间,接口返回带 `Z` 的 UTC 时间,页面按酒店或用户时区展示;酒店业务日期不做 UTC 时间点换算。
落地规则:
- 数据库中的时间点字段统一按 UTC 理解。
- Java Entity 中的 `LocalDateTime` 默认表示 UTC 时间点,不表示服务器本地时间。
- API 返回时间点字段必须是带 `Z` 的 ISO 8601 UTC 时间,例如 `2026-07-09T05:56:56Z`
- 前端展示时间点时,再按用户或酒店时区格式化;当前默认酒店时区是 `Asia/Bangkok`
- 入住日期、离店日期、酒店营业日等本地业务日期,不得和 UTC 时间点混用。
## 3. 时间字段分类
| 类型 | 示例字段 | 含义 | 数据库语义 | API 语义 | 页面展示 |
| --- | --- | --- | --- | --- | --- |
| 系统审计时间 | `created_at``updated_at``occurred_at``confirmed_at``completed_at` | 本系统内部动作发生时间 | UTC 时间点 | 带 `Z` 的 UTC 时间 | 按酒店或用户时区展示 |
| 外部来源事件时间 | SourceMessage `received_at``source_sent_at` | 外部系统提供的邮件接收 / 发送时间 | UTC 时间点 | 带 `Z` 的 UTC 时间 | 按酒店或用户时区展示 |
| 外部认证时间 | SuperAgent HMAC timestamp | 外部调用签名时间 | UTC 时间点 | ISO 8601 UTC | 不作为业务展示字段 |
| 酒店本地业务日期 | 入住日期、离店日期、营业日 | 酒店当地日历日期 | `LocalDate` 或明确本地日期语义 | 日期字符串 | 直接按酒店本地日期展示 |
## 4. 数据库存储规则
MySQL 时间点字段当前主要使用 `DATETIME(6)`
注意:`DATETIME(6)` 本身不带时区。本项目约定,所有表示时间点的 `DATETIME(6)` 都按 UTC 解释。
示例:
```text
数据库值2026-07-09 05:56:56
真实含义2026-07-09T05:56:56Z
泰国展示2026-07-09 12:56:56
```
后端连接 MySQL 时JDBC URL 应明确 `serverTimezone=UTC`。部署容器和 JVM 也应优先使用 UTC或至少保证入库时间都通过 UTC 生成。
## 5. Java 类型规则
后端内部约定:
- Entity / Snapshot 中从数据库读取的 `LocalDateTime` 默认表示 UTC 时间点。
- API Response 中的时间点字段优先使用 `OffsetDateTime`
- 数据库 `LocalDateTime` 输出到 API 时,使用 `UtcTimeFormatter.toUtcOffsetDateTime(...)` 统一补 UTC offset。
- 当前 UTC 时间统一使用 `LocalDateTime.now(ZoneOffset.UTC)``OffsetDateTime.now(ZoneOffset.UTC)`
- 酒店本地日期使用 `LocalDate`,不要使用 `LocalDateTime` 假装表示日期。
示例:
```java
OffsetDateTime apiTime = UtcTimeFormatter.toUtcOffsetDateTime(entity.getCreatedAt());
```
## 6. SourceMessage 邮件时间规则
`platform_source_message_inbox.received_at` 的语义已经调整为邮件来源接收时间:
1. 优先取 AgentBus payload 或 source 中的 `received_at`
2. 如果 AgentBus 没有提供或格式无法解析,回退为本系统捕获该消息的 UTC 时间。
3. `created_at` / `updated_at` 仍表示本系统记录创建 / 更新时间,不随邮件来源时间变化。
4. `source_sent_at` 表示来源系统提供的邮件发送时间,缺失时为空。
因此:
- `received_at` 用于邮件会话排序和业务展示。
- `created_at` / `updated_at` 用于系统审计和排查链路处理时间。
- 不应把 `received_at``created_at` 强行要求相等。
## 7. API 返回规则
后端返回给前端的时间点字段统一是 UTC 字符串:
```json
{
"source_received_at": "2026-07-09T05:56:56Z",
"created_at": "2026-07-09T05:57:00Z"
}
```
接口字段名保持业务语义:
- `received_at` / `source_received_at`邮件来源接收时间UTC。
- `source_sent_at`邮件来源发送时间UTC。
- `created_at`本系统记录创建时间UTC。
- `updated_at`本系统记录更新时间UTC。
前端不能把这些带 `Z` 的时间直接当泰国本地时间显示。
## 8. 页面展示规则
页面展示时间点时,应按酒店或用户时区格式化。当前酒店默认时区是 `Asia/Bangkok`
示例:
```text
API 返回2026-07-09T05:56:56Z
泰国时间2026-07-09 12:56:56
```
前端可以使用 `Intl.DateTimeFormat`
```ts
new Intl.DateTimeFormat("zh-CN", {
timeZone: "Asia/Bangkok",
dateStyle: "short",
timeStyle: "medium",
}).format(new Date("2026-07-09T05:56:56Z"));
```
业务人员最终应看页面展示时间;数据库只用于排查 UTC 事实时间。
## 9. 按酒店本地日期筛选
如果用户按酒店本地日期筛选,例如查询泰国当地 `2026-07-09` 的邮件,后端查询数据库前必须把本地日期范围转换成 UTC 范围。
泰国时区示例:
```text
Asia/Bangkok 2026-07-09 00:00:00
-> UTC 2026-07-08 17:00:00
Asia/Bangkok 2026-07-10 00:00:00
-> UTC 2026-07-09 17:00:00
```
查询建议使用左闭右开:
```sql
received_at >= '2026-07-08 17:00:00'
AND received_at < '2026-07-09 17:00:00'
```
不要直接用数据库 UTC 日期的 `2026-07-09 00:00:00 ~ 23:59:59` 当作泰国当地一天。
## 10. 排查规则
排查时间问题时,先确认要看的是什么:
| 场景 | 应看字段 | 判断方式 |
| --- | --- | --- |
| 业务人员看到邮件接收时间 | API `received_at` 或页面展示值 | API 是 UTC页面转酒店时区 |
| 判断 AgentBus 是否传了邮件接收时间 | 原始 payload `received_at` | 应是 ISO 8601 UTC例如 `2026-07-09T05:56:56Z` |
| 判断本系统什么时候写入记录 | `created_at` | UTC 系统审计时间 |
| 判断链路处理延迟 | `created_at - received_at` | 两者都按 UTC 理解后再比较 |
| 判断入住 / 离店日期 | 业务日期字段 | 不做 UTC 时间点换算 |
## 11. 禁止事项
- 不要把数据库 `DATETIME(6)` 直接当酒店当地时间展示。
- 不要在数据库里同时保存一份 UTC 时间和一份泰国展示时间。
- 不要把入住日期、离店日期、营业日用 UTC 时间点自动前后偏移。
- 不要在前端用中文或英文展示文案判断时间语义,应按接口字段名和文档约定处理。
- 不要在日志里输出包含客户敏感信息的完整邮件正文或附件 URL时间字段可以输出但要带上下文说明其 UTC 语义。