32 KiB
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 API(
caches.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-08compatibility_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.ts 的 fetch 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 | 元数据 JSON(ua, 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_at:
sortSources/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)
- 全局错误处理:
app.onError→ JSON{status:"failed",error:{code:500,...}}+ 结构化日志 - CORS preflight:
OPTIONS *返回允许的方法和头 - 安全头:
applySecurityHeaders对每个响应添加 CSP / Referrer-Policy / X-Content-Type-Options / X-Frame-Options / Permissions-Policy - CORS:
applyCorsHeaders根据 origin 白名单(空则不加 CORS 头) - 下载域名隔离: fetch handler 前置 hostname 检查
5.2 管理 API (/api/*)
全局中间件:
requireAdmin: Bearer token / querytoken/x-sub-store-token头 → SHA-256 +timingSafeEqual与SUB_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 (/download/*)
路由: GET /download/collection/:name/:target?/:token? 和 GET /download/source/:name/:target?/:token?
鉴权:
- 先校验全局
SUB_STORE_PUBLIC_DOWNLOAD_TOKEN(timingSafeEqual) - 不通过则查
download_grants表(token_hash 匹配 + enabled + 未过期 + resource/target 匹配) - 都不通过 → 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)
输入内容自动检测格式:
- JSON/JSON5 数组 (
[或{开头):parseJsonProxies— 支持{proxies: [...]}或顶层数组 - YAML (
proxies:开头):parseYamlProxies— 提取proxies字段 - URI 行 (默认):
parseProxyLines— 逐行解析
decodeMaybeBase64 先检测是否为结构化内容,否则尝试 base64 解码。
支持的 URI 协议 (parseProxyUri):
vless://— VLESS + Realityvmess://— VMess (base64 JSON)trojan://— Trojanss://— Shadowsocks (base64 或明文 userinfo)ssr://— ShadowsocksR (base64)hysteria:///hy://— Hysteria v1hysteria2:///hy2://— Hysteria v2tuic://— TUIC v5anytls://— AnyTLSsocks:///socks5:///socks5+tls://— SOCKS5http:///https://— HTTP proxywireguard:///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-port → mixedPort),然后删除 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 cache,cacheStatus=stale - 强制刷新: query
refresh=1或noCache=1跳过缓存读取
12. 并发控制
async function runWithConcurrency<T>(tasks, concurrency, waitMs)
- 固定数量 worker 池,从共享 cursor 取任务
waitMs: 每个任务前延迟(仅 index > 0 时),防止突发请求concurrency: 默认 3,范围 1-12ignoreFailed=true用runSettledWithConcurrency(Promise.allSettled 语义),失败源跳过ignoreFailed=false用runWithConcurrency,任一失败则整体 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 部署方式
- Deploy to Cloudflare 按钮: 用根
wrangler.jsonc,CF 自动创建 D1 - Agent/CLI 安装器:
pnpm run install:cloudflare,支持导入源/集合 - 快速安装:
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 核心移植清单
- 数据库层: 5 张表 schema 直接复用(SQLite 方言基本兼容),UPSERT 改为 Go 风格
- 订阅管线:
subscription.ts的解析/过滤/渲染逻辑需完整移植(最大工作量) - 协议解析: 13 种 URI 协议 + 客户端配置行解析器
- 过滤器: 11 种 FilterRule 类型的执行逻辑
- 渲染器: 13 种目标格式的渲染器
- 路由模板: 6 套内置模板 + 模板渲染(proxy-group 展开)
- 规则转换: rules.ts 逻辑
- 脚本系统: 构建时注册表模式可保留(Go plugin 或代码生成),运行时 sandbox 需重新设计
- 缓存: 远程源 HTTP 缓存需自建(带 etag/304/stale 语义)
- 并发控制: worker 池模式可用 goroutine + semaphore 实现
- 安全: 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. 关键设计决策总结
- 内置模板代码持有: 避免迁移同步问题,所有部署即时获得模板修复
- 源级 + 集合级双重过滤: 先对每个源独立过滤,再对合并后的节点做集合级过滤
- ignoreFailed 语义: 集合级配置,决定多源获取是 allSettled 还是 all
- created_at 排序: 用时间戳整数排序,sort 操作批量更新 created_at
- scoped grants: 分享令牌可限定资源+目标+过期,hash 存储
- 有界回收站: 50 条上限自动裁剪,防止无限增长
- 构建时脚本: 安全(无运行时 eval)但灵活性受限
- stale cache 降级: 远程源失败时用过期缓存保证可用性
- 目标兼容性过滤: 不支持的协议类型自动跳过,而非报错
- 临时覆盖: 下载时可通过 query 临时替换源 URL/content/UA,用于调试