# 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-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.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) 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 + `timingSafeEqual` 与 `SUB_STORE_ADMIN_TOKEN` 比对 - `bodyLimit`: 4 MiB (`MAX_API_BODY_BYTES`) **统一响应格式**: - 成功: `{ "status": "success", "data": }` - 失败: `{ "status": "failed", "error": { "code": , "message": } }` **端点清单**: | 方法 | 路径 | 功能 | |------|------|------| | 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/:token` 和 `GET /sources/:name/:token`,目标格式通过 `?target=` 指定 **鉴权**: 1. 先校验全局 `SUB_STORE_PUBLIC_DOWNLOAD_TOKEN`(timingSafeEqual) 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 运行时执行 ```typescript type ScriptRuntime = { arguments: Record; // 参数(含默认值) options: Record; // 原始 options targetPlatform: SubscriptionTarget; // 目标客户端 context: Record; // 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 模板结构 ```typescript type RoutingTemplateConfig = { mixedPort?: number; // 7890 allowLan?: boolean; // false mode?: string; // "rule" logLevel?: string; // "info" dns?: Record; // fake-ip + DoH sniffer?: Record; proxyGroups?: TemplateProxyGroup[]; // 10 个默认组 ruleProviders?: Record; // 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/` - **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. 并发控制 ```typescript async function runWithConcurrency(tasks, concurrency, waitMs) ``` - 固定数量 worker 池,从共享 cursor 取任务 - `waitMs`: 每个任务前延迟(仅 index > 0 时),防止突发请求 - `concurrency`: 默认 3,范围 1-12 - `ignoreFailed=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 构建管线 ```bash 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.jsonc`,CF 自动创建 D1 2. **Agent/CLI 安装器**: `pnpm run install:cloudflare`,支持导入源/集合 3. **快速安装**: `pnpm run install:quick`,先部署后配置 ### 14.3 数据库迁移 ```bash 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,用于调试