Skip to content

VMax 消息服务端系统全景

1. 文档定位

本文描述首版消息服务端、OpenAPI、密文离线队列、Push Gateway 与部署运维边界。它不冻结密码协议、规范化编码、身份地址、备份文件格式、开源许可或生产安全声明。

1.1 事实与状态

  • 来源 MUST:首版为同一实例、单设备、一对一文本通信;服务端、对象存储和 Push Gateway 不得读取消息明文或私钥。
  • 来源 MUST:OpenAPI 3.1 是客户端与服务端共享契约;未知协议版本和不安全降级必须拒绝。
  • 来源 MUST:消息实例支持带 TTL 的密文离线投递、稳定游标拉取、幂等写入、事务性 ACK 与删除。
  • 来源 MUST:Push Gateway 只处理无内容唤醒;完全主权部署必须使用独立 Bundle ID 和自有 APNs 凭据。
  • 已选架构,尚未实现验证:服务端使用 Go,容器编排交付使用 Docker Compose,PostgreSQL 保存权威业务状态,Redis 承载可重建的后台任务队列,附件范围使用 S3 兼容对象存储。
  • MVP 启用范围:只启用一对一文本消息闭环;Redis 队列用于唤醒、过期和清理任务;S3 接口与适配层可以预留,但图片、视频、文件上传及对象存储服务默认关闭。
  • 产品边界:不建设 Web 管理后台;实例初始化、迁移、健康检查和策略变更通过受控配置与一次性管理 CLI 完成。
  • 已验证事实:截至本文编写时,仓库只有文档与目录骨架,没有可运行服务端、数据库迁移、OpenAPI 文件或部署清单,因此本文所有运行行为均为设计目标,不是实测结论。

VMax iOS 客户端、Go 消息实例、PostgreSQL、Redis、Push Gateway 与 APNs 的系统架构图

图示用于审阅信任边界和数据流;下文是可访问的文字说明。查看图示说明

2. 系统与信任边界

text
iOS 客户端
  ├─ 本地身份/设备私钥、会话状态、消息明文
  ├─ HTTPS REST:发现、注册、认证、预密钥、提交、拉取、ACK
  └─ WSS:前台新消息提示;最终一致性仍依赖拉取


TLS 入口 Caddy/Nginx/Traefik

          ├─ vmax-api
          │    ├─ Identity/Auth
          │    ├─ Prekey
          │    ├─ Message Relay
          │    └─ Push Route
          ├─ vmax-worker(消费 Redis 任务队列)
          │    ├─ PostgreSQL outbox 转发/补偿
          │    ├─ TTL/ACK 清理
          │    ├─ 任务重试
          │    └─ 无内容唤醒派发
          ├─ PostgreSQL(系统记录、公开材料、密文信封)
          ├─ Redis(后台任务队列、短期限流、在线态)
          └─ S3 兼容存储(已选架构;P1 附件密文,MVP 关闭)


          Push Gateway(可选) ── APNs

2.1 客户端拥有的秘密

客户端负责消息明文、身份与设备私钥、会话密钥、备份密码、端到端加解密、联系人验证和协议状态。服务端不得要求上传这些数据,也不得通过诊断接口、错误回显或管理员工具获取它们。

2.2 消息实例职责

消息实例负责实例发现、公开身份材料、设备签名认证、公开预密钥、密文信封路由、短期离线队列、ACK、配额、限流和运维健康。它可以观察必要元数据,例如接收设备、密文大小、接收时间和过期时间;文档与 UI 必须如实说明该元数据边界。

2.3 Push Gateway 职责

Push Gateway 只接受不可反查消息内容的 wake_route_id 和通用唤醒请求,不接收密文、发送方、会话 ID、实例完整域名或附件信息。Push 只是提示客户端拉取,不是消息投递证明。

3. 首版消息时序

  1. 发送端在本机产生唯一 message_id 并完成端到端加密。
  2. 发送端用 Idempotency-Key 调用 POST /v1/messages
  3. API 在一个数据库事务内校验认证、版本、大小、TTL 与配额,记录幂等结果并持久化密文信封。
  4. 提交成功后,服务端返回接收时间与队列过期时间;此时客户端可显示“已发送”。
  5. 同一事务写入 PostgreSQL outbox;转发器把任务发布到 Redis 队列,Worker 产生只含随机路由引用的唤醒。Gateway/APNs 或 Redis 失败不得改变信封已持久化事实,补偿扫描会重新发布未完成 outbox。
  6. 接收端通过稳定游标拉取密文,验证并解密;拉取本身不删除信封。
  7. 接收端仅在成功解密后提交 delivered ACK。服务端原子记录 ACK 并将信封置为可清理。
  8. 重复提交、重复拉取和重复 ACK 均返回一致结果;客户端以 message_id 去重。

4. 可用性与一致性原则

  • PostgreSQL 是消息、幂等、ACK、outbox 和清理状态的权威存储。Redis 是已选的任务队列实现,但不得成为消息、ACK 或待执行业务意图的唯一存储。
  • Worker 对 Redis 任务采用至少一次消费;任务带稳定 job_id 和业务幂等键。Redis 数据丢失后由 PostgreSQL outbox 补偿重建,允许重复执行但不得丢失业务结果。
  • WSS 和 Push 是提示路径;Redis 断开时 API 可以继续完成受控的核心事务,但 readiness/告警必须反映后台任务停滞,恢复后补偿。客户端仍可通过 HTTPS 游标拉取已持久化信封。
  • 接口采用至少一次传输语义,通过全局消息 ID、幂等键和重复 ACK 达成用户可见的“只显示一次”。不得宣称底层恰好一次投递。
  • 服务端接收顺序只用于稳定分页和诊断,不定义端到端协议消息顺序。
  • 过期和清理由数据库时间判定;客户端时间不参与协议安全判断。

5. 部署模式概览

模式消息实例PushBundle ID/APNs能否称为完全主权
官方官方运营官方 Gateway官方
普通私有用户自托管官方 Gateway 或关闭官方 App
完全主权用户自托管自有 Gateway独立 Bundle ID 与凭据是,仍受 Apple/APNs 平台边界约束

不得把“消息存储在私有实例”与“推送链路完全自主”混为一谈。详细部署与威胁边界见 docs/operations/部署模式.md 和 ADR-006。

6. 版本边界

  • HTTP API 使用 /v1 主版本;兼容规则以 OpenAPI 契约和 docs/architecture/兼容性与迁移.md 为准。
  • E2EE protocol_version 与 HTTP API 版本分离。服务端只做长度、枚举、能力和路由校验,不解析协议正文。
  • 实例发现响应公布支持的 API/协议范围、功能能力和策略版本,并由实例长期签名密钥签名。
  • 密码套件、canonical encoding、身份地址和备份格式与本文交叉的字段只保留版本槽位,不在此冻结。

7. 后端组件设计

7.1 Go 服务边界

  • vmax-api:无状态 HTTP/WSS 入口,完成认证、校验、PostgreSQL 事务与 outbox 写入,不在请求事务内等待 Push/S3。
  • vmax-worker:消费 Redis 队列,执行 outbox 转发确认、TTL/ACK 清理、Push 派发和补偿任务。
  • vmax-cli:一次性初始化、迁移、配置签名、备份校验和健康诊断;不常驻、不提供 Web 管理界面,也不得查看聊天内容。
  • Go 内部模块按 authidentityprekeysmessagesqueuepushobjectspolicyobservability 分层;协议密文保持 opaque bytes。

7.2 队列可靠性

已选链路为 PostgreSQL 事务 outbox -> Redis 任务队列 -> Go Worker。API 与业务行在同一事务写 outbox,独立 dispatcher 领取并发布;Worker 成功执行后用稳定任务 ID 回写完成状态。定时 reconciler 扫描超时的 pending/published 记录并安全重发。

Redis 队列库、消费确认机制和延迟任务实现仍为待总控决策,但必须满足可见性超时/租约、指数退避、死信隔离、最大尝试次数、并发限流和幂等 handler。不得依赖 Redis 持久化配置来替代 PostgreSQL outbox。

7.3 S3 兼容对象存储

S3 兼容存储是总体架构组件,但不进入 MVP 文本闭环。MVP 的实例发现必须返回 attachments=false,Compose 默认不启动对象存储,附件端点不接受生产请求。P1 启用时只保存客户端加密后的对象,文件密钥仍只存在于 E2EE 消息中;服务端不生成明文缩略图、不扫描明文。

8. 待总控决策

8.1 Redis 队列实现

  • 选项:成熟 Go 队列库、Redis Streams 消费组或自建最小队列协议。
  • 取舍:库可减少重试/调度实现量但引入许可和升级风险;Streams 更透明但需要自行正确实现租约、重领和延迟队列。
  • 建议:先以故障语义和许可证筛选成熟库,不自行发明分布式队列;选择需同步 ADR-009 与 SBOM。
  • 验证:Redis 重启/清空、Worker 在确认前后崩溃、重复投递、延迟任务、毒任务和网络分区下,PostgreSQL 业务状态最终正确。

8.2 密文信封外层编码

  • 待总控决策:JSON + Base64URL、CBOR 或其他 canonical encoding。
  • 取舍:JSON 易调试但体积较大;CBOR 更紧凑但 canonical 规则与跨语言工具链必须验证。
  • 建议:服务端契约先把信封视为有大小上限的 opaque bytes;最终编码跟随 ADR-004。
  • 验证:Swift/Go 跨语言固定向量、未知字段、长度边界、重复字段与非规范编码拒绝测试。

9. 验证门槛

  • 集成测试覆盖提交成功但客户端超时、重复提交、并发拉取、重复 ACK、乱序、过期与配额隔离。
  • PostgreSQL 重启、磁盘满、迁移中断、Redis 清空、Worker 崩溃和 APNs 不可用时不得丢失已确认持久化的信封或永久遗漏清理任务。
  • MVP 启动与契约测试确认 attachments=false,对象存储未启动时文本、认证、预密钥和消息接口全部正常。
  • 数据库、对象存储、日志、指标、追踪、备份和诊断导出执行敏感数据扫描,不得发现明文或私钥。
  • 未完成可运行实现、压测、恢复演练、渗透测试和独立密码学审计前,不得宣称已达到生产安全或发布门槛。

系统初步设计 · 待决事项不代表批准 · 安全方案尚未完成独立审计