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

6.7 KiB
Raw Permalink Blame History

后端时间设计说明

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_atupdated_atoccurred_atconfirmed_atcompleted_at 本系统内部动作发生时间 UTC 时间点 Z 的 UTC 时间 按酒店或用户时区展示
外部来源事件时间 SourceMessage received_atsource_sent_at 外部系统提供的邮件接收 / 发送时间 UTC 时间点 Z 的 UTC 时间 按酒店或用户时区展示
外部认证时间 SuperAgent HMAC timestamp 外部调用签名时间 UTC 时间点 ISO 8601 UTC 不作为业务展示字段
酒店本地业务日期 入住日期、离店日期、营业日 酒店当地日历日期 LocalDate 或明确本地日期语义 日期字符串 直接按酒店本地日期展示

4. 数据库存储规则

MySQL 时间点字段当前主要使用 DATETIME(6)

注意:DATETIME(6) 本身不带时区。本项目约定,所有表示时间点的 DATETIME(6) 都按 UTC 解释。

示例:

数据库值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 假装表示日期。

示例:

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_atcreated_at 强行要求相等。

7. API 返回规则

后端返回给前端的时间点字段统一是 UTC 字符串:

{
  "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

示例:

API 返回2026-07-09T05:56:56Z
泰国时间2026-07-09 12:56:56

前端可以使用 Intl.DateTimeFormat

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 范围。

泰国时区示例:

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

查询建议使用左闭右开:

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 语义。