Files
pangolin/doc/02-backend.html
T
wangjia 3ae96ef229 docs: 新增整体架构设计文档(doc/,HTML 七章)
覆盖前端五端/后端/MySQL/弹性节点拓扑/Web安全/安全总纲,遵循 design/ 设计系统视觉呈现。

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

175 lines
11 KiB
HTML
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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>02 后端设计 · 穿山甲架构设计</title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<header class="site-header"><div class="inner">
<a class="brand" href="index.html"><img src="assets/logo-mark.svg" alt="Pangolin"><span><span class="zh">穿山甲 · 架构设计</span><br><span class="en">Pangolin Architecture</span></span></a>
<nav class="site-nav">
<a href="index.html">总览</a>
<a href="01-frontend.html">01 前端</a>
<a href="02-backend.html" class="active">02 后端</a>
<a href="03-database.html">03 数据库</a>
<a href="04-infra.html">04 网络拓扑</a>
<a href="05-web-security.html">05 Web 安全</a>
<a href="06-security.html">06 安全总纲</a>
</nav>
<button class="theme-btn" id="themeBtn" title="切换深浅主题"><i data-lucide="moon"></i></button>
</div></header>
<main class="doc">
<div class="doc-hero">
<div class="overline">Chapter 02 · Backend</div>
<h1>后端设计</h1>
<p class="lede">Go 模块化单体,满足前端全部功能即可、不做过度设计。API 契约完整继承 <code>design/server/ARCHITECTURE.md</code> §3 蓝本,唯一语义修订:数据面从 WireGuard 换为 sing-boxREALITY / Hysteria2),<code>connect</code> 改为下发用户凭证 + 节点连接参数。</p>
</div>
<section id="modules">
<h2><span class="num">§1</span>服务形态与模块划分</h2>
<p>单体起步、按模块分包(实现顺序即蓝本 §7):</p>
<div class="diagram">
<div class="flow">
<div class="fbox accent"><b>HTTP API</b><span>chi · /v1</span></div>
<div class="farr"></div>
<div class="fbox"><b>auth</b><span>验证码/注册/JWT</span></div>
<div class="fbox"><b>codes</b><span>激活码/兑换/webhook</span></div>
<div class="fbox"><b>devices</b><span>设备/订阅校验</span></div>
<div class="fbox"><b>nodes</b><span>目录/connect/调度</span></div>
<div class="fbox"><b>usage</b><span>用量/额度</span></div>
<div class="fbox"><b>admin</b><span>管理端(独立监听)</span></div>
</div>
<div class="flow">
<div class="fbox ghost"><b>gRPC servermTLS</b><span>节点 agent:注册 / 心跳 / 凭证下发与回收 / 用量上报</span></div>
<div class="fbox ghost"><b>scheduler</b><span>探针汇聚 · 判封 · 节点生命周期驱动(见 04 章)</span></div>
<div class="farr"></div>
<div class="fbox"><b>MySQL</b><span>主数据</span></div>
<div class="fbox"><b>Redis</b><span>验证码/限流/实时负载</span></div>
</div>
<div class="cap">一个二进制三个监听:公网 API(CDN 后)· agent gRPCmTLS 双向认证)· 管理端(内网/白名单)</div>
</div>
<ul>
<li>每模块完成定义(蓝本约定):单测 + OpenAPI 同步 + 错误文案双语(<code>{code, message_zh, message_en}</code>+ 符合脱敏口径。</li>
<li>管理端绝不与公网 API 共用监听端口;部署时仅绑内网地址,前置白名单 + 2FA(见 06 章)。</li>
</ul>
</section>
<section id="api">
<h2><span class="num">§2</span>API 契约 v1(继承蓝本 §3,正式版用 OpenAPI 定义)</h2>
<pre><code><span class="c"># 认证(无需 JWT</span>
POST /v1/auth/code {email} <span class="c"># 发 6 位验证码(Redis 10min,限 1/min</span>
POST /v1/auth/register {email, code, password} <span class="c"># 建号 + 7 天试用 + JWT</span>
POST /v1/auth/login {email, password} <span class="c"># JWTaccess 15min + refresh 30d</span>
POST /v1/auth/refresh {refresh_token}
<span class="c"># 账户</span>
GET /v1/me <span class="c"># 账户 + 订阅 + 用量摘要</span>
GET /v1/me/devices
DELETE /v1/me/devices/:id <span class="c"># 移除设备(同步回收节点侧凭证)</span>
<span class="c"># 商业闭环(App 内无支付,只有兑换)</span>
POST /v1/redeem {code} <span class="c"># 兑换激活码(幂等 + 审计)</span>
POST /v1/ads/unlock {device_id, ad_token} <span class="c"># 免费版激励视频解锁当日时长(验 SDK 回执)</span>
GET /v1/plans <span class="c"># 套餐目录(数字与 design/CLAUDE.md §7 一致)</span>
<span class="c"># 节点(数据面入口)</span>
GET /v1/nodes ?if_version=N <span class="c"># 节点目录(按套餐过滤,304 支持)</span>
POST /v1/nodes/:id/connect {device_id} <span class="c"># 下发连接凭证(见 §3,语义已适配 sing-box)</span>
POST /v1/nodes/:id/disconnect {device_id}
GET /v1/usage ?days=7 <span class="c"># 用量曲线(统计页)</span>
GET /v1/notices <span class="c"># 公告(亦发布为多镜像签名静态 JSON)</span></code></pre>
<ul>
<li>除 auth 外全部接口要求 JWT;限流用 Redis 滑动窗口(按 IP + 用户双维度)。</li>
<li>错误体统一 <code>{code, message_zh, message_en}</code>;文案遵守铁律 13 脱敏口径。</li>
<li>节点目录响应带 <code>version</code>;客户端 <code>if_version</code> 命中返回 304——这是节点秒级灰度的基础(见 04 章)。</li>
</ul>
</section>
<section id="connect">
<h2><span class="num">§3</span>connect 语义适配(相对蓝本 v0.1 的唯一修订)</h2>
<div class="callout"><b>修订理由</b>蓝本 v0.1 的数据面是 WireGuardconnect = 下发 WG peer)。WG 协议特征明显,在目标网络环境会被快速识别阻断;plan/ phase-0~3 已选定 REALITY 主线 + Hysteria2 备线。API 形状不变,仅返回体语义调整。</div>
<h3>3.1 连接流程</h3>
<div class="diagram">
<div class="flow">
<div class="fbox accent"><b>客户端</b><span>POST /nodes/:id/connect</span></div>
<div class="farr"></div>
<div class="fbox"><b>API 校验</b><span>订阅有效 · 设备数 ≤ 上限 · 免费版校验 ad_unlocked_at + 剩余分钟</span></div>
<div class="farr"></div>
<div class="fbox"><b>gRPC → agent</b><span>确保该用户 UUID 已在节点 sing-box 用户表</span></div>
<div class="farr"></div>
<div class="fbox ok"><b>返回连接参数</b><span>server, port, uuid, flow, reality pbk/sid/sni · hy2 备用</span></div>
</div>
<div class="cap">免费版凭证带 TTL = 当日剩余分钟,到时 agent 自动从用户表移除;付费版 TTL 24h 在线自动续期</div>
</div>
<h3>3.2 凭证模型</h3>
<ul>
<li><strong>每用户一个数据面 UUID</strong>(与账号 ID 解耦,可轮换):节点侧只见 UUID,零账号信息——单节点被抄不泄露用户库。</li>
<li>凭证轮换:用户改密 / 管理端封禁 / 定期轮换时生成新 UUID,gRPC 广播到所有在册节点替换;旧 UUID 给 5 分钟宽限期平滑重连。</li>
<li>订阅过期、设备被移除、套餐降级 → scheduler 驱动 agent 即时回收对应 UUID。</li>
<li>Hysteria2 凭证同源派生(同一 UUID 作 auth password),客户端按网络状况自动选协议。</li>
</ul>
</section>
<section id="flows">
<h2><span class="num">§4</span>关键业务流程</h2>
<h3>4.1 注册与试用(对齐客户端 UI:邮箱 → 验证码 → 设密码)</h3>
<ul>
<li><code>POST /auth/code</code>:风控(IP + 邮箱限频、一次性邮箱域黑名单)→ 发码(Redis 10min)。</li>
<li><code>POST /auth/register</code>:验码 → 建号 → <strong>自动写入 7 天 PRO 试用</strong><code>subscriptions(plan=pro, expires_at=now()+7d, source='trial')</code>,一邮箱仅一次)→ 返回 JWT。</li>
<li>试用到期回落 free:仅 tier=free 节点、每日 10 分钟、每日首连前需激励视频解锁。</li>
</ul>
<h3>4.2 激活码生命周期</h3>
<div class="diagram">
<div class="flow">
<div class="fbox"><b>发卡店售出</b><span>webhookHMAC 签名)</span></div>
<div class="fbox"><b>人工渠道</b><span>TG/LINE/邮箱 · 管理端批量生成 batch</span></div>
<div class="farr"></div>
<div class="fbox"><b>codes 入库</b><span>status=unused · 库存 hash</span></div>
<div class="farr"></div>
<div class="fbox accent"><b>POST /redeem</b><span>事务:redeemed + 订阅顺延(叠加非覆盖)</span></div>
<div class="farr"></div>
<div class="fbox"><b>audit_log</b><span>必记</span></div>
</div>
</div>
<ul>
<li>码格式:Crockford Base32,16 位含校验位;明文只出现一次(生成响应 / 发卡店),库内存 hash。</li>
<li>兑换幂等:同一用户重复提交同一码返回首次结果;风控:单用户失败 5 次/小时锁 1 小时。</li>
</ul>
<h3>4.3 免费版广告解锁</h3>
<ul>
<li>客户端播完激励视频 → <code>POST /v1/ads/unlock</code> 携带广告 SDK 回执 → 服务端向 AdMob/Unity 校验回执真伪 → 记 <code>usage_daily.ad_unlocked_at</code></li>
<li><code>connect</code> 对 free 用户强制校验当日 <code>ad_unlocked_at</code> 与剩余分钟;分钟数由 agent 上报的会话时长累计,服务端为权威。</li>
</ul>
</section>
<section id="hardening">
<h2><span class="num">§5</span>后端安全要点(详见 06 章总纲)</h2>
<ul>
<li>密码 argon2id;JWT RS256,签名密钥定期轮换;全站 TLS 1.3。</li>
<li>验证码 / 兑换 / 登录接口限流 + 防一次性邮箱 + 失败锁定;所有敏感操作进 <code>audit_log</code></li>
<li><strong>无日志口径</strong>(写进隐私政策并据实执行):不记录目的地址 / DNS 查询 / 流量内容;仅 <code>usage_daily</code> 字节数与分钟数。</li>
<li>agent gRPC 双向 mTLS:节点证书由内部 CA 签发、与节点 ID 绑定,节点被回收即吊销。</li>
<li>CIlint + 单测 + OpenAPI 校验 + 镜像构建;OpenAPI 即 API 文档与客户端 SDK 生成源。</li>
</ul>
</section>
</main>
<nav class="pager">
<a href="01-frontend.html"><span>上一章</span><b>← 01 前端架构</b></a>
<a class="next" href="03-database.html"><span>下一章</span><b>03 数据库 →</b></a>
</nav>
<footer class="colophon">穿山甲 · Pangolin — 内部架构设计文档 · 遵循 design/ 设计系统 · 2026-06</footer>
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
<script src="assets/doc.js"></script>
</body>
</html>