Files
sub-store/docs/architecture-analysis.md

32 KiB
Raw Permalink Blame History

Sub-Store Cloudflare 原项目技术实现架构分析

参考路径: ref/sub-store-cloudflare/ 版本: 1.1.0 (AGPL-3.0-or-later) 仓库: github.com/realchendahuang/sub-store-cloudflare


1. 项目定位

Sub-Store Cloudflare 是一个 Cloudflare 原生的机场订阅聚合与路由模板管理工具,部署形态为 Cloudflare Workers + Static Assets + D1 + Worker Secrets。核心能力:

  • 订阅源聚合:管理多个远程/本地订阅源,合并为集合 (Collection)
  • 节点处理:解析 13 种代理协议 URI、JSON/YAML 数组、客户端配置行,执行过滤/排序/去重/重命名等操作
  • 多目标输出:转换为 mihomo / sing-box / surge / loon / qx / egern / shadowrocket / v2ray / uri 等 13 种客户端格式
  • 路由模板:内置 6 套 ACL4SSR / Loyalsoldier / AI+Streaming mihomo 模板,支持自定义
  • 规则转换:将分流规则在 mihomo/surge/loon/qx 之间互转
  • 分享/回收站scoped download grants(限定资源+目标+过期的分享令牌)、有界回收站(50 条上限)

非功能边界AGENTS.md 明确约束):

  • 不使用 R2/KV/Durable Objects/Queues/Cron
  • 不运行时 eval 脚本(脚本为构建时打包)
  • 不存储/执行来自 D1/浏览器/远程 URL 的脚本源码
  • 远程源缓存使用 Workers Cache APIcaches.default),非 D1

2. 整体架构

┌─────────────────────────────────────────────────────────────┐
│                    Cloudflare Worker                         │
│  (cloudflare/src/index.ts — Hono app, single entry)          │
│                                                              │
│  ┌─────────────┐    ┌──────────────┐    ┌────────────────┐  │
│  │ /api/*      │    │ /download/*  │    │  Static Assets │  │
│  │ (admin API) │    │ (public dl)  │    │  (SPA fallback)│  │
│  │ routes/api  │    │ routes/dl    │    │  ASSETS binding│  │
│  └──────┬──────┘    └──────┬───────┘    └────────────────┘  │
│         │                  │                                 │
│         ▼                  ▼                                 │
│  ┌────────────────────────────────────────────────────────┐ │
│  │                    lib/ 层                              │ │
│  │  store.ts        subscription.ts    rules.ts            │ │
│  │  (D1 CRUD)       (核心管线)          (规则转换)          │ │
│  │  defaults.ts     targets.ts         scripts.ts           │ │
│  │  (内置模板)      (目标别名)          (脚本注册表)         │ │
│  │  compatibility   http.ts            limits.ts  read.ts  │ │
│  │  -resources.ts   (token/CORS/Hdr)   (常量)     (流读)   │ │
│  └──────────────────────┬─────────────────────────────────┘ │
│                         │                                    │
│         ┌───────────────┼───────────────┐                   │
│         ▼               ▼               ▼                   │
│  ┌────────────┐  ┌────────────┐  ┌──────────────┐           │
│  │ D1 (DB)    │  │ Cache API  │  │ Worker Secrets│          │
│  │ 5 张表     │  │ caches.    │  │ ADMIN_TOKEN  │           │
│  │            │  │ default    │  │ DOWNLOAD_TOK │           │
│  └────────────┘  └────────────┘  └──────────────┘           │
└─────────────────────────────────────────────────────────────┘

2.1 Monorepo 结构

sub-store-cloudflare/
├── package.json              # workspace root, pnpm@11.7, node>=22
├── pnpm-workspace.yaml       # packages: frontend, cloudflare
├── wrangler.jsonc            # 顶层部署配置 (ASSETS + D1 + secrets)
├── cloudflare/               # Worker 后端
│   ├── src/
│   │   ├── index.ts          # Hono app 入口 + 导出 ExportedHandler
│   │   ├── types.ts          # 所有 TypeScript 类型定义
│   │   ├── worker-configuration.d.ts  # Wrangler 生成的 Env 接口
│   │   ├── routes/
│   │   │   ├── api.ts        # /api/* 管理接口 (896 行)
│   │   │   └── download.ts   # /download/* 公开下载接口
│   │   ├── lib/
│   │   │   ├── store.ts              # D1 数据层 CRUD (644 行)
│   │   │   ├── subscription.ts       # 核心订阅处理管线 (2430 行)
│   │   │   ├── rules.ts              # 分流规则转换
│   │   │   ├── defaults.ts           # 6 套内置路由模板
│   │   │   ├── targets.ts            # 客户端目标别名归一化
│   │   │   ├── scripts.ts            # 构建时脚本注册表 + 运行时
│   │   │   ├── compatibility-resources.ts  # download_grants + recycle_bin
│   │   │   ├── http.ts               # token 校验 / CORS / 安全头
│   │   │   ├── limits.ts             # 6 个字节/数量限制常量
│   │   │   └── read.ts               # 流式响应读取 (防 OOM)
│   │   └── generated/        # 构建时生成 (git ignored)
│   │       └── script-registry.ts
│   ├── migrations/
│   │   ├── 0001_initial.sql
│   │   ├── 0002_runtime_schema_cleanup.sql
│   │   └── 0003_compatibility_resources.sql
│   └── package.json          # hono, json5, yaml 三个运行时依赖
├── frontend/                 # Vue 3 + Vite SPA
│   └── src/                  # Pinia stores, views, components
└── scripts/                  # 构建时 Node.js 脚本
    ├── generate-script-registry.mjs   # 扫描 config/scripts → registry
    ├── install-cloudflare.mjs          # 一键安装器
    ├── render-seed-sql.mjs             # agent-setup → seed SQL
    └── ... (15+ 检查/部署脚本)

3. 运行时环境与绑定

3.1 Worker Bindings (wrangler.jsonc)

绑定 类型 用途
DB D1Database 所有结构化配置数据
ASSETS Fetcher Static Assets (前端 SPA, run_worker_first: true)
SUB_STORE_ADMIN_TOKEN Secret (string) /api/* 管理 API 鉴权
SUB_STORE_PUBLIC_DOWNLOAD_TOKEN Secret (string) /download/* 公开下载鉴权
SUB_STORE_APP_NAME Var (string) 应用显示名
SUB_STORE_PUBLIC_DOWNLOAD_HOSTS Var (string) 逗号分隔的纯下载域名列表

3.2 关键配置

  • compatibility_date: 2026-07-08
  • compatibility_flags: ["nodejs_compat"]
  • assets.not_found_handling: "single-page-application" — 前端 SPA 路由兜底
  • assets.run_worker_first: true — Worker 先处理请求,未匹配才回退到静态资源
  • observability: enabled, head_sampling_rate=1

3.3 下载域名隔离机制

index.tsfetch handler 在路由之前检查 hostname

  • 如果当前 hostname 在 SUB_STORE_PUBLIC_DOWNLOAD_HOSTS 列表中,且 pathname 不以 /download/ 开头 → 返回 404
  • 效果:下载域名只能访问下载端点,管理 UI 不可达

4. 数据库 Schema (D1 / SQLite)

4.1 表结构

sources — 订阅源

类型 说明
id TEXT PK 1-64 字符 [a-z0-9_-]
name TEXT 显示名
type TEXT remote / local
url TEXT 远程 URL(可多行,换行分隔,最多 8 个)
content TEXT local 源的原始内容
enabled INTEGER 0/1
filters_json TEXT FilterRule[] 的 JSON
meta_json TEXT 元数据 JSONua, cacheTtl, subUserinfo 等)
created_at INTEGER 毫秒时间戳,同时用于排序
updated_at INTEGER 毫秒时间戳

collections — 订阅集合

类型 说明
id TEXT PK 同上
name TEXT 显示名
source_ids_json TEXT string[],空数组表示包含所有 enabled 源
filters_json TEXT 集合级 FilterRule[]
template_id TEXT 关联的路由模板 ID
ignore_failed INTEGER 0/1,是否忽略失败的源
enabled INTEGER 0/1
meta_json TEXT 元数据
created_at / updated_at INTEGER 同上

templates — 路由模板(仅存自定义的,内置模板在代码中)

类型 说明
id TEXT PK 同上
name TEXT 显示名
target TEXT 目标客户端(默认 mihomo
config_json TEXT RoutingTemplateConfig JSON
created_at / updated_at INTEGER 同上

app_settings — 应用设置(单行,id="default"

类型 说明
id TEXT PK 固定 "default"
value_json TEXT 设置 JSON(深合并)
updated_at INTEGER

download_grants — 分享令牌(migration 0003

类型 说明
id TEXT PK UUID
token_hash TEXT UNIQUE SHA-256 hex of token
resource_type TEXT CHECK source / collection
resource_id TEXT
target TEXT 限定的目标客户端,空=不限
expires_at INTEGER 过期时间,NULL=永不过期
enabled INTEGER 0/1
created_at / updated_at INTEGER
  • 索引: idx_download_grants_token_hash, idx_download_grants_resource

recycle_bin — 回收站(migration 0003

类型 说明
id TEXT PK UUID
resource_type TEXT CHECK source/collection/template/share
resource_id TEXT
snapshot_json TEXT 删除时的完整快照
deleted_at INTEGER
  • 索引: idx_recycle_bin_deleted_at
  • 上限: 50 条(通过 DELETE ... LIMIT -1 OFFSET 50 自动裁剪)

4.2 设计要点

  • 排序用 created_atsortSources / sortCollections 通过 UPDATE SET created_at = now + index 重排序,列表查询 ORDER BY created_at ASC。同一 batch 中 now + index 保证顺序。
  • UPSERT 模式:所有写入用 INSERT ... ON CONFLICT(id) DO UPDATE SET ...,天然幂等。
  • 批量操作importStorage 使用 env.DB.batch(statements) 单事务导入。
  • 内置模板不入库migration 0002 主动删除旧版内置模板行,改为代码持有 (BUILTIN_TEMPLATES 数组)listTemplates 合并内置+自定义。

5. HTTP 路由设计

5.1 全局中间件 (index.ts)

  1. 全局错误处理: app.onError → JSON {status:"failed",error:{code:500,...}} + 结构化日志
  2. CORS preflight: OPTIONS * 返回允许的方法和头
  3. 安全头: applySecurityHeaders 对每个响应添加 CSP / Referrer-Policy / X-Content-Type-Options / X-Frame-Options / Permissions-Policy
  4. CORS: applyCorsHeaders 根据 origin 白名单(空则不加 CORS 头)
  5. 下载域名隔离: fetch handler 前置 hostname 检查

5.2 管理 API (/api/*)

全局中间件:

  • requireAdmin: Bearer token / query token / x-sub-store-token 头 → SHA-256 + timingSafeEqualSUB_STORE_ADMIN_TOKEN 比对
  • bodyLimit: 4 MiB (MAX_API_BODY_BYTES)

统一响应格式:

  • 成功: { "status": "success", "data": <T> }
  • 失败: { "status": "failed", "error": { "code": <number>, "message": <string> } }

端点清单:

方法 路径 功能
GET /api/env 运行时环境信息(backend、version、feature flags
GET /api/scripts 构建时脚本注册表元数据
POST /api/proxy/parse 一次性代理内容转换
POST /api/rule/parse 一次性分流规则转换
GET/PATCH /api/settings 应用设置(GET 合并默认值)
GET/POST /api/storage 全量导出/导入(备份恢复)
GET/POST /api/sources 订阅源列表/创建
PUT /api/sources 批量排序
POST /api/sort/sources 排序(兼容旧接口)
GET/PATCH/DELETE /api/sources/:name 单源操作
GET/POST /api/collections 集合列表/创建
PUT /api/collections 批量排序
GET/PATCH/DELETE /api/collections/:name 单集合操作
GET/POST /api/templates 模板列表/创建
GET/PATCH/DELETE /api/templates/:name 单模板操作(内置不可删改)
GET/POST /api/shares 分享令牌列表/创建
PATCH/DELETE /api/shares/:id 令牌操作
GET /api/recycle-bin 回收站列表
DELETE /api/recycle-bin/:id 彻底删除
POST /api/recycle-bin/:id/restore 恢复
POST /api/utils/node-info IP 信息查询(代理转发 ipwho.is 等)
POST /api/preview/source 源预览(原始+处理后节点)
POST /api/preview/collection 集合预览
GET /api/link/source/:name 生成下载链接
GET /api/link/collection/:name 生成下载链接
GET /api/source/flow/:name 流量信息(解析 subscription-userinfo 头)

5.3 下载 API

路由: GET /collections/:name/:tokenGET /sources/:name/:token,目标格式通过 ?target= 指定

鉴权:

  1. 先校验全局 SUB_STORE_PUBLIC_DOWNLOAD_TOKENtimingSafeEqual
  2. 不通过则查 download_grants 表(token_hash 匹配 + enabled + 未过期 + resource/target 匹配)
  3. 都不通过 → 403

target 推断:

  • 显式: URL path param 或 query target
  • 隐式: User-Agent 推断(sing-box/v2ray/surge/loon/egern/shadowrocket/quantumult/stash → 对应 target,默认 mihomo

临时覆盖: query 参数 url / content / ua 可临时覆盖源配置(不持久化),用于预览/调试。

响应头: content-type 按 target 分配(YAML / JSON / text/plain),subscription-userinfo / profile-web-page-url / content-disposition / x-sub-store-cache 透传。


6. 核心订阅处理管线 (subscription.ts)

这是整个项目最核心的模块(2430 行),负责从订阅源到客户端格式的完整转换。

6.1 管线流程

buildSubscriptionResult(options)
  │
  ├─ getSources(options)
  │    ├─ 单源模式: [options.source]
  │    └─ 集合模式: 按 collection.sourceIds 过滤 options.sources
  │         (sourceIds 为空 = 所有 enabled 源)
  │
  ├─ loadProxyNodes(internal)  ──────────────────────────────┐
  │    │                                                       │
  │    ├─ 过滤 enabled 源                                      │
  │    │                                                       │
  │    ├─ 并发任务 (runWithConcurrency / runSettledWithConcurrency)
  │    │    每个 source:                                       │
  │    │      ├─ loadSubscriptionRaw(sub, settings, ua, runtime)
  │    │      │    ├─ local: 直接返回 content
  │    │      │    └─ remote: splitSourceUrls (最多8个URL)
  │    │      │         并发 fetchSubscriptionUrl (每个URL)
  │    │      │           ├─ Cache API 查缓存 (cacheTtl, 默认300s)
  │    │      │           ├─ fetch with UA + If-None-Match/Modified
  │    │      │           ├─ 304 → 用缓存 (cacheStatus=refresh)
  │    │      │           ├─ !ok → throw
  │    │      │           ├─ 读 body (限 2MiB/URL, 总 12MiB)
  │    │      │           ├─ 写缓存 (waitUntil, 非阻塞)
  │    │      │           └─ error → stale cache if allowed
  │    │      │         合并多 URL 内容, decodeMaybeBase64
  │    │      │                                              │
  │    │      ├─ parseProxies(raw)  ← 解析为 ProxyNode[]      │
  │    │      └─ applyFilters(nodes, source.filters, ...)     │
  │    │           (源级过滤)                                  │
  │    │                                                       │
  │    ├─ ignoreFailed=true: runSettled (失败源跳过)           │
  │    │  ignoreFailed=false: runWithConcurrency (失败则整体报错)
  │    │                                                       │
  │    ├─ flat() 合并所有源节点                                │
  │    ├─ applyFilters(allNodes, collection.filters, ...)      │
  │    │    (集合级过滤)                                       │
  │    └─ ensureUniqueProxyNames (重名加 -2, -3 后缀)          │
  │                                                            │
  ├─ renderBuildTarget(proxies, options) ─────────────────────┘
  │    按 target 分发到对应渲染器
  │
  └─ selectResponseMetadata(internal)
       从第一个有效源提取 subscription-userinfo 等元数据

6.2 代理协议解析 (parseProxies)

输入内容自动检测格式:

  1. JSON/JSON5 数组 ([{ 开头): parseJsonProxies — 支持 {proxies: [...]} 或顶层数组
  2. YAML (proxies: 开头): parseYamlProxies — 提取 proxies 字段
  3. URI 行 (默认): parseProxyLines — 逐行解析

decodeMaybeBase64 先检测是否为结构化内容,否则尝试 base64 解码。

支持的 URI 协议 (parseProxyUri):

  • vless:// — VLESS + Reality
  • vmess:// — VMess (base64 JSON)
  • trojan:// — Trojan
  • ss:// — Shadowsocks (base64 或明文 userinfo)
  • ssr:// — ShadowsocksR (base64)
  • hysteria:// / hy:// — Hysteria v1
  • hysteria2:// / hy2:// — Hysteria v2
  • tuic:// — TUIC v5
  • anytls:// — AnyTLS
  • socks:// / socks5:// / socks5+tls:// — SOCKS5
  • http:// / https:// — HTTP proxy
  • wireguard:// / wg:// — WireGuard

支持的客户端配置行 (parseClientProxyLine):

  • Quantumult X 格式: shadowsocks=host:port,... / vmess=host:port,...
  • Surge/Loon 格式: name = ss,host,port,... / name = vmess,host,port,...
  • 支持协议: ss, ssr, vmess, vless, trojan, http, socks5, hysteria2, tuic, anytls, snell, ssh, h2-connect

6.3 过滤器系统 (applyFilters)

FilterRule.type 支持 11 种操作,按顺序串行执行:

type 功能 关键字段
include 保留匹配项 pattern (正则), field (默认 name)
exclude 排除匹配项 同上
rename 正则替换 pattern, replacement, field
delete-field 删除字段匹配内容 patterns[]pattern, field
dedupe 去重 fields[] (默认 name), action: delete/rename
sort 排序 direction: asc/desc/random
regex-sort 正则优先级排序 expressions[], direction
flag 国旗添加/移除 mode: add/remove, tw: cn/tw/ws
quick 快速设置 udp, tfo, scert, useless
resolve DNS 解析域名→IP provider, recordType, filter
script 构建时脚本 scriptId, scriptKind, arguments

路径访问: getByPath / setByPath 支持点号路径 (如 ws-opts.headers.Host)。

正则编译: compileRegex 支持 (?i) 前缀作为大小写不敏感标志。

国旗检测 (detectFlag): 8 个地区正则 → emoji 旗帜,台湾旗可配置为 cn/tw/ws。

DNS 解析 (resolveProxyDomains): 支持 Google/Cloudflare/Ali/Tencent/Custom DoH,并发解析,可过滤失败/IP类型,保留原域名为 servername/sni。

6.4 目标渲染 (renderBuildTarget)

目标 渲染器 输出格式 Content-Type
mihomo / stash renderMihomoYaml YAML (proxies + proxy-groups + rules) text/yaml
surge renderSurgeProxies 文本行 text/plain
surge-mac renderSurgeMacProxies 文本行 (含 ssh/h2-connect/snell) text/plain
surfboard renderSurfboardProxies 文本行 (ss/vmess/trojan/http/socks5) text/plain
loon renderLoonProxies 文本行 text/plain
egern renderEgernYaml YAML ({proxies: [...]}) text/yaml
qx renderQxProxies 文本行 text/plain
sing-box renderSingBoxJson JSON (outbounds + route) application/json
v2ray renderProxyUris + base64 base64 编码的 URI 行 text/plain
uri / shadowrocket renderProxyUris URI 行 text/plain
json 直接 JSON.stringify {proxies: [...]} application/json

mihomo YAML 渲染 特殊处理:

  • proxy-groups 中的 $all 展开为所有节点名
  • filter 正则匹配节点名
  • 引用不存在的组/节点会被过滤
  • DIRECT / REJECT 为允许的字面量

sing-box JSON 渲染:自动生成 PROXY (selector) + AUTO (urltest) + DIRECT + REJECT outbound,附带 mixed-in inbound 和 route 配置。

目标兼容性 (isTargetCompatible): 每种目标只输出它支持的协议类型,不支持的节点被跳过。


7. 脚本系统 (scripts.ts + generate-script-registry.mjs)

7.1 构建时注册表

脚本不存储在 D1 中,而是在构建时从 config/script-plugins.json(公开)和 config/script-plugins.local.json(个人)扫描,生成 cloudflare/src/generated/script-registry.ts

约束:

  • 最多 32 个脚本,每个最大 32 KiB
  • 不能使用 import/export/eval/require/new Function
  • 公开脚本不能使用 fetch/$httpClient/setTimeout
  • 必须声明 function filter(...)function operator(...)
  • 脚本代码通过 node --check 语法检查

7.2 运行时执行

type ScriptRuntime = {
  arguments: Record<string, unknown>;  // 参数(含默认值)
  options: Record<string, unknown>;    // 原始 options
  targetPlatform: SubscriptionTarget;  // 目标客户端
  context: Record<string, unknown>;    // sourceId, collectionId, scriptId
  proxyUtils: typeof PROXY_UTILS;      // 工具函数
  substore: { env: "Cloudflare" };
};

两种脚本类型:

  • filter: 返回 boolean[](每个节点一个布尔值),保留 true 的节点
  • operator: 返回 ProxyNode[](可修改节点,但不能增加数量)

PROXY_UTILS 提供: isIPv4, isIPv6, isIP, removeFlag, Base64.encode/decode

限制: 每个 source/collection 最多 2 个 script action (MAX_SCRIPT_ACTIONS = 2)。


8. 路由模板系统 (defaults.ts)

8.1 内置模板(6 套)

ID 名称 特点
mihomo-basic Mihomo Basic 最简,内联规则
acl4ssr-mihomo ACL4SSR Mihomo 默认推荐,17 个 rule-provider
acl4ssr-mihomo-no-emoji ACL4SSR 无 Emoji 同上去 emoji
loyalsoldier-whitelist Loyalsoldier 白名单 代理优先
loyalsoldier-blacklist Loyalsoldier 黑名单 直连优先
ai-streaming-mihomo AI + Streaming AI/流媒体/TG/GitHub 路由

8.2 模板结构

type RoutingTemplateConfig = {
  mixedPort?: number;          // 7890
  allowLan?: boolean;          // false
  mode?: string;               // "rule"
  logLevel?: string;           // "info"
  dns?: Record<string, unknown>;      // fake-ip + DoH
  sniffer?: Record<string, unknown>;
  proxyGroups?: TemplateProxyGroup[]; // 10 个默认组
  ruleProviders?: Record<string, unknown>; // http provider
  rules?: string[];                   // RULE-SET 规则链
};

proxy-group 特殊值:

  • $all → 展开为所有节点名
  • filter → 正则筛选节点
  • 引用其他组名 → 自动包含

8.3 模板别名归一化

normalizeMihomoTemplateConfig 将 kebab-case 别名拷贝到 camelCase(如 mixed-portmixedPort),然后删除 kebab-case 键。


9. 规则转换 (rules.ts)

将分流规则在 mihomo / surge / loon / qx 之间互转。

支持的规则类型: DOMAIN, DOMAIN-SUFFIX, DOMAIN-KEYWORD, IP-CIDR, IP-CIDR6, GEOIP, GEOSITE, PROCESS-NAME, DST-PORT, MATCH

别名归一化: HOST→DOMAIN, HOST-SUFFIX→DOMAIN-SUFFIX, IPCIDR→IP-CIDR, FINAL→MATCH, DEST-PORT→DST-PORT 等

QX 特殊处理: DOMAIN→HOST, IP-CIDR6→IP6-CIDR, MATCH→FINAL,只保留 no-resolve 选项


10. 安全机制

10.1 Token 鉴权

  • Admin Token: SHA-256 双向 hash + crypto.subtle.timingSafeEqual(防时序攻击)
  • Download Token: 同上
  • Scoped Grant Token: 24 字节随机 → base64url,数据库存 SHA-256 hex,明文只返回一次

10.2 安全头

Content-Security-Policy: default-src 'self'; ... script-src 'self' 'unsafe-eval'; ...
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Permissions-Policy: camera=(), microphone=(), ...

10.3 输入限制

限制 位置
API body 4 MiB MAX_API_BODY_BYTES
远程源 URL 数 8 MAX_REMOTE_SOURCE_URLS
单 URL 响应 2 MiB MAX_REMOTE_SOURCE_RESPONSE_BYTES
源总响应 12 MiB MAX_REMOTE_SOURCE_TOTAL_BYTES
流量信息响应 64 KiB MAX_FLOW_RESPONSE_BYTES
DoH 响应 64 KiB MAX_DOH_RESPONSE_BYTES
回收站条目 50 MAX_RECYCLE_ENTRIES
脚本数 32 MAX_SCRIPTS
脚本大小 32 KiB MAX_SCRIPT_BYTES
脚本 action 2 MAX_SCRIPT_ACTIONS
ID 长度 1-64 [a-z0-9_-] validateRecordId

10.4 流式读取 (read.ts)

readResponseText 先检查 Content-Length,再流式读取并累计字节,超限则 cancel reader 并抛错——防止 OOM。


11. 缓存策略

远程订阅源使用 Workers Cache API (caches.default) 缓存:

  • 缓存键: SHA-256(url\n userAgent) → https://sub-store-cache.invalid/source/<hash>
  • TTL: cacheTtl 设置(源 meta 或全局 settings),默认 300s,范围 0-3600s
  • 缓存内容: 响应体 + 自定义头(subscription-userinfo, etag, last-modified 等)
  • 缓存命中: cacheStatus=hit
  • 304 Not Modified: 用缓存内容,cacheStatus=refresh
  • 新鲜获取: 写缓存 (waitUntil)cacheStatus=miss
  • 错误降级: remoteCacheStaleOnError !== false 时用 stale cachecacheStatus=stale
  • 强制刷新: query refresh=1noCache=1 跳过缓存读取

12. 并发控制

async function runWithConcurrency<T>(tasks, concurrency, waitMs)
  • 固定数量 worker 池,从共享 cursor 取任务
  • waitMs: 每个任务前延迟(仅 index > 0 时),防止突发请求
  • concurrency: 默认 3,范围 1-12
  • ignoreFailed=truerunSettledWithConcurrencyPromise.allSettled 语义),失败源跳过
  • ignoreFailed=falserunWithConcurrency,任一失败则整体 reject

13. 前端架构概览

13.1 技术栈

  • Vue 3 + TypeScript + Vite
  • Pinia (状态管理)
  • NutUI (组件库)
  • CodeMirror (代码编辑器)
  • 8 套主题 (light/dark/pureblack/darkblue/lightblue/sereneblues/monokai/mocha)

13.2 核心交互

  • Admin Token: 存 localStorage,从 URL ?token= 同步并清除
  • API 基址: VITE_API_URL=/(同源),构建时注入
  • 环境检测: /api/env 返回 backend 类型(Cloudflare/Node/Docker/各客户端),前端据此显示图标和功能开关
  • 设置合并: 前端合并后端默认设置 + 用户设置(mergeSettings

13.3 页面结构

  • Sub / SubEditor — 订阅源列表与编辑器
  • Collection (隐含在 Sub 页) — 集合管理
  • Preview — 节点预览
  • CompareTable — 节点对比
  • Tools — 工具页
  • My — 个人/设置
  • editCode/cmView — 代码编辑器

14. 构建与部署

14.1 构建管线

pnpm run build
  ├─ scripts:generate  → generate-script-registry.mjs
  │    扫描 config/script-plugins.json + .local.json
  │    → cloudflare/src/generated/script-registry.ts
  └─ build:frontend    → Vite build → frontend/dist/

14.2 部署方式

  1. Deploy to Cloudflare 按钮: 用根 wrangler.jsoncCF 自动创建 D1
  2. Agent/CLI 安装器: pnpm run install:cloudflare,支持导入源/集合
  3. 快速安装: pnpm run install:quick,先部署后配置

14.3 数据库迁移

pnpm run db:migrations:apply
  → wrangler d1 migrations apply DB --remote --config ../wrangler.jsonc

3 个 migration

  • 0001: 创建 sources/collections/templates/app_settings + 默认 daily 集合
  • 0002: 清理旧内置模板行 + 重建 app_settings
  • 0003: 添加 download_grants + recycle_bin

15. Go + SQLite 重构要点

基于以上分析,Go 重构需关注:

15.1 对应关系

原项目 Go 重构
Cloudflare Workers Go HTTP server (net/http 或框架)
D1 (SQLite) SQLite (mattn/go-sqlite3 或 modernc.org/sqlite)
Hono 路由库 (chi/gin/echo 等)
Workers Cache API 自建缓存层 (内存 LRU 或 Redis)
Worker Secrets 环境变量/配置文件
Static Assets embed.FS 或独立静态服务
crypto.subtle.timingSafeEqual crypto/subtle.ConstantTimeCompare
caches.default 需自建 HTTP 缓存层

15.2 核心移植清单

  1. 数据库层: 5 张表 schema 直接复用(SQLite 方言基本兼容),UPSERT 改为 Go 风格
  2. 订阅管线: subscription.ts 的解析/过滤/渲染逻辑需完整移植(最大工作量)
  3. 协议解析: 13 种 URI 协议 + 客户端配置行解析器
  4. 过滤器: 11 种 FilterRule 类型的执行逻辑
  5. 渲染器: 13 种目标格式的渲染器
  6. 路由模板: 6 套内置模板 + 模板渲染(proxy-group 展开)
  7. 规则转换: rules.ts 逻辑
  8. 脚本系统: 构建时注册表模式可保留(Go plugin 或代码生成),运行时 sandbox 需重新设计
  9. 缓存: 远程源 HTTP 缓存需自建(带 etag/304/stale 语义)
  10. 并发控制: worker 池模式可用 goroutine + semaphore 实现
  11. 安全: timingSafeEqual / 安全头 / 输入限制 / 流式读取

15.3 架构差异点

  • 无 Cache API: Go 版需自建缓存层(内存或 Redis),原项目依赖 Workers Cache API 的特性需要替代
  • 无 waitUntil: Go 版用 goroutine + context,缓存写入可异步但需管理生命周期
  • 脚本沙箱: 原项目用 JS 闭包在 Worker 中执行,Go 版需要 Go plugin / WASM / 嵌入式 JS 引擎 (goja) 等方案
  • 静态资源: 原 run_worker_first + SPA fallback 在 Go 中需显式实现路由优先级
  • D1 batch: Go SQLite 用事务替代

16. 关键设计决策总结

  1. 内置模板代码持有: 避免迁移同步问题,所有部署即时获得模板修复
  2. 源级 + 集合级双重过滤: 先对每个源独立过滤,再对合并后的节点做集合级过滤
  3. ignoreFailed 语义: 集合级配置,决定多源获取是 allSettled 还是 all
  4. created_at 排序: 用时间戳整数排序,sort 操作批量更新 created_at
  5. scoped grants: 分享令牌可限定资源+目标+过期,hash 存储
  6. 有界回收站: 50 条上限自动裁剪,防止无限增长
  7. 构建时脚本: 安全(无运行时 eval)但灵活性受限
  8. stale cache 降级: 远程源失败时用过期缓存保证可用性
  9. 目标兼容性过滤: 不支持的协议类型自动跳过,而非报错
  10. 临时覆盖: 下载时可通过 query 临时替换源 URL/content/UA,用于调试