平台架构与数据库设计

返回主题索引 · 返回 cmmuu 统一平台

1. 需求摘要

cmmuu 不只是一套网站代码,而是多个产品共用的基础平台:

  • 主站:cmmuu.com,负责品牌、产品入口、用户总览和统一管理。
  • 账号中心:例如 account.cmmuu.com,统一处理 Apple、Google、GitHub 与邮箱登录。
  • 产品服务:例如 reqforge.cmmuu.com,未来继续增加其他工具和订阅服务。
  • 管理后台:管理用户、产品、套餐、订阅、订单、权限、运营配置和审计日志。
  • 订阅隔离:每个产品可独立定义免费、Pro、Super,以及月付、年付价格和权益。
  • 当前不加入账户充值或余额体系,避免提前引入资金账本、退款、过期余额和合规复杂度。

2. 目标架构

flowchart TD
    User["国内与海外用户"] --> Edge["Cloudflare DNS、CDN 与 Tunnel"]
    Edge --> Web["TypeScript 前端<br/>主站与产品子域名"]
    Web --> Auth["统一账号中心<br/>OIDC 与 OAuth"]
    Web --> Api["Go API<br/>统一鉴权与业务路由"]
    Auth --> Pg["PostgreSQL<br/>账户、订阅、订单与审计"]
    Api --> Pg
    Api --> Redis["Redis<br/>缓存、会话与限流"]
    Api --> Object["Cloudflare R2<br/>对象文件与异地备份"]
    Pg --> Backup["WAL-G 或 pgBackRest<br/>基础备份与 WAL"]
    Backup --> Object
    Object --> Local["广州 N100 或 NAS<br/>只读拉取与本地快照"]

2.1 前端

统一使用 TypeScript,但不要求所有页面运行在同一个前端进程中:

  • apps/web-main:cmmuu 主站。
  • apps/account-web:登录、绑定身份、账号安全和授权确认页面。
  • apps/admin-web:统一管理后台。
  • apps/reqforge-web:AI 前置产品。
  • 后续每个产品新增一个前端应用,共享组件、设计令牌、SDK 和鉴权包。

账号中心需要前端页面,但不需要独立仓库;它可以作为 Monorepo 中的独立应用,通过独立域名部署。

2.2 Go 后端

早期优先采用模块化单体,而不是立即拆成微服务:

  • identity:账号、外部身份、邮箱验证和会话。
  • catalog:产品、套餐、价格和权益定义。
  • subscription:订阅状态、周期和权益发放。
  • billing:订单、支付回调、退款状态和幂等事件。
  • entitlement:运行时权限判定。
  • admin:后台管理接口和审计。

模块之间通过明确接口和事务边界隔离,先共用一个 Go 进程和一个 PostgreSQL 集群。只有当团队规模、发布频率、负载或故障域出现明确冲突时,再拆分独立服务。

3. OIDC 与统一登录

OIDC 是 OpenID Connect,即建立在 OAuth 2.0 之上的身份认证协议。OAuth 2.0 主要解决“允许应用访问什么资源”,OIDC 补充“当前用户是谁”。

统一登录流程:

flowchart LR
    Product["产品子域名"] --> Account["account.cmmuu.com"]
    Account --> Provider["Apple、Google、GitHub 或邮箱"]
    Provider --> Account
    Account --> Code["一次性授权码"]
    Code --> Product
    Product --> Session["产品会话与统一用户标识"]

安全边界:

  • 浏览器授权码流程使用 Authorization Code + PKCE。
  • 严格校验 statenonce 和回调地址白名单。
  • Apple、Google、GitHub 的客户端密钥只保存在服务端密钥存储中。
  • 外部平台账号只作为 identity,内部统一映射到不可变的 user_id
  • 同一邮箱不自动合并账号;必须通过已登录状态或二次验证进行身份绑定。
  • 子域名不共享高权限后台 Cookie;后台会话使用更短过期时间和多因素认证。

4. PostgreSQL 数据模型

PostgreSQL 是事务型事实来源,关键表建议如下:

核心用途关键隔离字段
users平台统一用户id
identitiesApple、Google、GitHub、邮箱身份映射user_idproviderprovider_subject
oauth_clients主站和各产品的 OIDC 客户端service_idredirect_uri
sessions登录会话和刷新令牌摘要user_idclient_id
servicescmmuu 下的独立产品idslug
plans免费、Pro、Super 等套餐service_idcode
prices月付、年付及币种价格plan_idbilling_periodcurrency
subscriptions用户在某产品的订阅状态user_idservice_idplan_id
entitlements套餐实际授予的功能与配额service_idplan_idfeature_key
orders下单、支付和退款状态user_idservice_ididempotency_key
payment_events支付渠道回调的不可重复事件providerprovider_event_id
audit_logs后台敏感操作审计actor_idservice_idaction

4.1 订阅隔离

  • 所有套餐、价格、订阅和权益都必须关联 service_id
  • 唯一约束使用组合键,例如 UNIQUE(service_id, code),防止不同产品的 pro 套餐相互覆盖。
  • 权益查询至少同时校验 user_idservice_id、订阅有效期和套餐状态。
  • 支付回调先按 provider_event_id 做幂等判断,再在单个数据库事务中更新订单、订阅和权益。
  • API 层从可信路由或令牌声明解析 service_id,不直接相信客户端提交的产品标识。
  • 早期使用同库同 schema 加行级隔离即可;出现法规、客户专属数据库或明显故障域需求后,再考虑独立 schema 或独立数据库。

4.2 原子性与一致性

需要强一致的操作全部放在 PostgreSQL 事务中:

  1. 创建订单与冻结价格快照。
  2. 接收并去重支付事件。
  3. 更新订单状态。
  4. 创建或续期订阅。
  5. 写入权益变更和审计记录。

事件通知、邮件和缓存刷新通过 Outbox 表在事务提交后异步执行,避免“数据库已成功、消息未发送”或反向不一致。

5. Redis 的职责边界

Redis 适合:

  • 高频且可重建的缓存。
  • 短期会话、验证码、限流计数。
  • 分布式锁,但必须设置有效期并处理锁续期和重复执行。
  • 可重建的异步任务状态。

Redis 不适合成为以下数据的唯一来源:

  • 用户身份和账号绑定关系。
  • 订单、支付结果、订阅有效期。
  • 永久权益和审计记录。

因此默认不把 Redis 持久化文件作为核心备份对象;若未来确有不可重建数据,应先迁移到 PostgreSQL,再决定是否为 Redis 增加 AOF、快照和异地备份。

6. 代码仓库与 CI/CD

完整的单仓库目录、前后端边界、新产品接入和路径触发流水线见 项目仓库与前后端架构

当前阶段建议使用 Monorepo 统一管理:

apps/
  web-main/
  account-web/
  admin-web/
  reqforge-web/
  api/
packages/
  ui/
  auth-sdk/
  api-client/
  config/
deploy/
  compose/
  migrations/
  github-actions/

GitHub Actions 可按路径过滤触发构建:

  • 前端目录变化,只构建和部署对应前端。
  • apps/api 或数据库迁移变化,才构建 Go 镜像并执行后端流程。
  • 公共包变化,计算受影响应用后批量构建。
  • 数据库迁移独立审查,执行前备份,使用向前兼容的 expand-contract 流程。

GitHub 免费分钟按账号或组织的计费规则统计,不因 Monorepo 天然增加;真正影响消耗的是工作流运行时长、操作系统倍率、触发次数和缓存命中率。自托管 Runner 不消耗 GitHub 托管分钟,但需要承担服务器、隔离、补丁和供应链安全成本。

7. 部署边界

容器可以统一运行环境,但不能绕开承载它的计算资源:

  • Docker 运行在 VPS:支付 VPS 和运维成本。
  • 托管容器平台:按 CPU、内存、请求和流量支付平台费用。
  • Serverless 容器:空闲成本低,但冷启动、数据库连接和持续任务受平台约束。

目标部署方式:

  • TypeScript 前端:Cloudflare Pages、Vercel 或同类边缘托管。
  • Go API:与主要用户区域和 PostgreSQL 同地域部署,避免每次请求跨境访问数据库。
  • PostgreSQL:单一主库起步,定期备份;达到明确收入和可用性要求后再迁移托管高可用数据库。
  • Redis:与 Go API 同机房部署,不暴露公网,仅允许内网或本机访问。
  • Cloudflare:负责 DNS、CDN、TLS、WAF 和可选 Tunnel,不替代源站或数据库。

8. 当前基础设施的合理角色

节点当前条件建议角色
广州 N10016 GB 内存、1 TB 存储、无公网 IP、Tailscale现有开发与低负载服务;未来可作为备份拉取节点和恢复演练机
上海云主机4 核 4 GB、60 GB、有公网入口面向大陆用户的 API 入口或轻量业务节点
香港云主机2 核 1 GB、40 GBTunnel、Nginx、探活或静态转发,不承载 PostgreSQL 主库
新加坡 VPS待购买与测试面向海外用户的 Go API、PostgreSQL 与 Redis 同地域节点

若数据库主库位于新加坡,不应让所有大陆请求逐次跨境访问数据库;可以保持上海业务栈独立,或只将海外产品部署到新加坡。

9. 实施顺序

  1. 建立 servicesusersidentities 和 OIDC 客户端模型。
  2. 完成账号中心最小前端与 Apple、Google、GitHub、邮箱登录。
  3. 接入 reqforge,验证主站和子域名统一登录。
  4. 建立套餐、价格、订阅、权益和支付事件幂等模型。
  5. 建立统一管理后台和审计日志。
  6. 完成 PostgreSQL PITR 备份和恢复演练。
  7. 新产品只新增产品模块、前端和权益定义,复用平台能力。

10. 主要风险

  • 身份误合并:邮箱相同不代表同一个外部账号。
  • 订阅串权:遗漏 service_id 会导致跨产品访问。
  • 支付重复回调:缺少幂等键会重复发放权益。
  • Redis 当事实库:重启或淘汰会丢失关键业务状态。
  • 跨境数据库:时延和网络抖动会放大每个 API 请求的尾延迟。
  • 单机故障:Docker Compose 不等于高可用,恢复能力依赖已验证的备份。