Files
pangolin/design/server/ARCHITECTURE.md
T
wangjia a642bf16a2 feat: 同步 design/ 设计系统(含 iPad tablet kit) + 架构任务拆分(todo/)
design/ 同步自最新设计导出,新增 ui_kits/tablet/ 平板分栏布局;todo/ 录入 18 个并行实施任务。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:57:57 +08:00

7.8 KiB

穿山甲 · 后端架构设计(初稿 v0.1)

本文档是给 Claude Code 的实现蓝本:先审阅 → 修订 → 再按模块实现。 产品语境见 ../CLAUDE.md(设计系统)。脱敏规则同样适用于日志、报错文案与对外文档。


0. 设计原则

  1. 控制面 / 数据面彻底分离:API 服务器只管账户、套餐、节点目录、兑换;用户流量只走加速节点(WireGuard),绝不经过 API。
  2. App 内无支付:资金流全部外部化(发卡店/TG/LINE/邮箱)→ 后端只做「激活码」的生成、分发对账与兑换。
  3. 节点可秒级灰度:节点列表带版本,被封节点能即时下架;客户端缓存最后一份可用列表兜底。
  4. 最小数据原则(无日志承诺):不记录浏览内容/目的地;只保留计费与风控所需的最小元数据(用量字节数、设备数、最后活跃时间),并在文档中公开口径。

1. 系统拓扑

┌──────────┐  HTTPS   ┌─────────────────┐
│ Flutter   │ ───────▶ │  API (控制面)    │── Postgres / Redis
│ 客户端    │          │  Go + chi/gin    │
└────┬─────┘          └───────┬─────────┘
     │ WireGuard UDP          │ 节点注册/心跳 (mTLS gRPC)
┌────▼─────────────┐  ┌───────▼─────────┐
│ 加速节点 (数据面)  │  │  发卡店 Webhook   │
│ wireguard + agent │  │  (激活码入库)     │
└──────────────────┘  └─────────────────┘
  • API:Go(推荐,wgctrl/生态成熟)。单体起步,按模块分包,未来可拆。
  • 节点 agent:每台加速节点跑一个轻量 agent:注册、心跳、上报负载、下发/回收 WireGuard peer。与 API 间用 mTLS gRPC。
  • 存储:Postgres(主数据)+ Redis(验证码、限流、节点实时负载)。

2. 数据模型(核心表)

users         (id, email UNIQUE, pw_hash, created_at, status)
devices       (id, user_id FK, name, platform, pubkey, last_seen, created_at)
plans         (id, code['free'|'pro'|'team'], max_devices, max_routes,
               daily_minutes NULL=无限, ad_gate bool)  -- free: 1 节点/10 分钟/须看广告
subscriptions (id, user_id FK, plan_id FK, expires_at, source['trial'|'code'], created_at)
codes         (id, code UNIQUE, plan_id, duration_days, batch_id, channel,
               status['unused'|'redeemed'|'void'], redeemed_by NULL, redeemed_at NULL)
code_batches  (id, channel['store'|'tg'|'line'|'manual'], created_by, note, created_at)
nodes         (id, region, name_zh, name_en, endpoint, pubkey, tags[], tier['free'|'pro'],
               status['up'|'down'|'draining'], weight)
usage_daily   (user_id, date, bytes_up, bytes_down, minutes_used, ad_unlocked_at NULL)
               -- 仅字节数/分钟数,无目的地;ad_unlocked_at = 当日激励视频解锁时刻(免费版)
audit_log     (id, actor, action, target, meta jsonb, at)  -- 兑换/封禁/节点操作必记

要点:

  • 试用 = 注册时自动插入 subscriptions(plan=pro, expires_at=now()+7d, source='trial'),一邮箱仅一次(套餐口径见 ../CLAUDE.md §7)。
  • 体验期后回落 free:仅 1 个基础节点(nodes.tier='free')、每日 10 分钟;每日首次连接前需激励视频解锁(客户端上报 → POST /v1/ads/unlockad_unlocked_at,connect 接口校验)。
  • codes.code 用 Crockford Base32,16 位,带校验位;明文只出现一次(生成响应/发卡店),库里可存 hash。

3. API 契约(v1 摘要,正式版用 OpenAPI 定义)

POST /v1/auth/code        {email}                  → 发 6 位验证码(Redis, 10min, 限 1/min)
POST /v1/auth/register    {email, code, password}  → 创建账户 + 7 天试用 + JWT
POST /v1/auth/login       {email, password}        → JWT (access 15min + refresh 30d)
POST /v1/auth/refresh     {refresh_token}

GET  /v1/me                                        → 账户 + 订阅 + 用量摘要
GET  /v1/me/devices                                → 设备列表
DELETE /v1/me/devices/:id                          → 移除设备(回收节点 peer)

POST /v1/redeem           {code}                   → 兑换激活码(幂等,审计)
POST /v1/ads/unlock       {device_id, ad_token}    → 免费版激励视频解锁当日时长(验广告 SDK 回执)
GET  /v1/plans                                     → 套餐目录(给客户端展示)

GET  /v1/nodes            ?if_version=N            → 节点目录(按套餐过滤, 304 支持)
POST /v1/nodes/:id/connect {device_pubkey}         → 下发 WireGuard 配置(peer 注册)
POST /v1/nodes/:id/disconnect

GET  /v1/usage            ?days=7                  → 用量曲线(统计页)

约定:错误体 {code, message_zh, message_en};全部接口 JWT(除 auth);限流用 Redis 滑窗。


4. 关键流程

4.1 注册(对齐客户端 UI:邮箱 → 验证码 → 设密码)

  1. POST /auth/code:风控(IP+邮箱限频、一次性邮箱域黑名单)→ 发码。
  2. POST /auth/register:验码 → 建号 → 自动开 7 天 PRO 试用 → 返回 JWT。

4.2 激活码生命周期

发卡店售出 → webhook(带签名) → codes 入库(status=unused, channel=store)
            人工渠道(TG/LINE/邮箱) → 管理端批量生成 batch → 导出给客服
客户端 POST /redeem → 校验+事务:code→redeemed, 订阅顺延(叠加而非覆盖) → audit_log
  • 兑换幂等:同一用户重复提交同一码返回首次结果。
  • 风控:单用户兑换失败 5 次/小时锁 1 小时。

4.3 连接(数据面)

  1. 客户端拉 /nodes(带套餐过滤 + 客户端测延迟自选)。
  2. POST /nodes/:id/connect:API 校验订阅与设备数(免费版另校验当日 ad_unlocked_at 与剩余分钟)→ 通过 gRPC 让节点 agent 添加 peer → 返回 WireGuard 配置(endpoint、server_pubkey、分配的内网 IP、DNS)。免费版 peer TTL = 剩余分钟数,到时 agent 自动回收。
  3. 心跳/续期:peer 默认 TTL 24h,客户端在线自动续;订阅过期或设备被移除 → agent 回收 peer。
  4. 节点 agent 每 30s 上报负载(在线 peer 数、带宽)→ Redis,用于目录排序与"信号条"。

4.4 节点目录灰度

  • nodes 任何变更 bump 全局 version;客户端带 if_version 轮询(或推送)。
  • 节点被封:status=down 立即从目录消失;draining 用于计划下线(不接新连接)。

5. 安全与合规

  • 密码 argon2id;JWT RS256,密钥轮换;全站 TLS1.3。
  • 验证码与兑换接口必须有人机/限流防刷;注册防一次性邮箱。
  • 无日志口径(写进隐私政策并据实执行):不记录目的地址/DNS 查询/流量内容;仅 usage_daily 字节数。
  • 管理端(内部)单独服务 + IP 白名单 + 双因素;一切敏感操作进 audit_log。
  • 节点服务器上不落用户身份明文,peer 只对应 device_id。

6. 部署与运维(起步规模)

  • API:单区域 2 实例 + LB;Postgres 托管(每日备份);Redis 托管。
  • 节点:按地区购 VPS(HK/JP/SG/US…),Ansible/脚本一键装 agent + WireGuard;出口 IP 池可轮换。
  • 监控:节点心跳缺失告警、兑换失败率、注册转化漏斗。
  • CI:GitHub Actions——lint + 单测 + OpenAPI 校验 + docker 镜像。

7. 给 Claude Code 的实现顺序

  1. openapi.yaml(把 §3 展开成正式契约)+ 数据库 migration
  2. auth 模块(验证码/注册/登录/JWT)+ 测试
  3. codes 模块(批量生成/webhook/兑换)+ 审计
  4. devices + 订阅校验中间件
  5. nodes 目录 + agent gRPC 协议(proto 文件)+ connect/disconnect
  6. usage 统计 + 管理端最小后台
  7. Flutter 端接 API(替换 UI Kit 演示数据;UI 规范见 design/)

每个模块完成的定义:有单测、有 OpenAPI 同步、错误文案双语且符合脱敏口径。