# sub-provider 项目上下文说明 本文档用于给后续接手的 Codex / 开发者快速建立上下文,理解这个项目为什么存在、当前已经做了什么、设计取舍是什么、下一步应该往哪里继续。 --- ## 1. 项目背景 用户当前的代理使用场景比较复杂: - Windows 主力客户端:**Mihomo Party / Clash Party** - iOS 客户端:**Stash** - 路由侧:**OpenClash** - 上游机场源不止一个,其中至少有一个已经变成了 **AnyTLS** - 过去主要依赖 `subconverter` / `subconverter-extended` / ACLSSR 模板体系做转换 用户遇到的问题是: 1. 上游协议变复杂后,传统模板式转换越来越脆弱。 2. 想统一管理多个机场源,但不想把所有逻辑继续绑死在单个大 YAML 里。 3. 希望同时兼容两种使用习惯: - 客户端直接拉一个完整配置文件 - 客户端拉一个轻量壳子,然后继续通过 provider 自动更新节点和规则 4. 希望保留订阅使用量 / 到期时间展示能力,即把上游订阅返回的 `Subscription-Userinfo` 头继续传给客户端。 因此,这个项目不是单纯的“订阅转换器”,而是一个 **订阅聚合与配置组装后端**。 --- ## 2. 本项目的核心目标 这个项目的目标分成两层: ### 2.1 内部目标 服务端内部继续**解耦维护**,不要回退成“一坨最终 YAML”。 内部要拆开维护: - 机场源定义 - 节点拉取与标准化 - provider 输出 - 规则文件 - 客户端入口配置 - bundle 单文件配置 - 配额头处理 ### 2.2 外部目标 对客户端同时提供两种模式: #### A. 薄壳模式 客户端拉: - `/clients/mihomo.yaml` - `/clients/stash.yaml` 特点: - 配置很薄 - 内部继续引用远程 `proxy-providers` / `rule-providers` - 节点和规则按 `interval` 自动更新 - 更适合 Mihomo / OpenClash 的长期维护 #### B. Bundle 模式 客户端拉: - `/bundle/mihomo.yaml` - `/bundle/stash.yaml` 特点: - 服务端把 `proxies`、`proxy-groups`、`rules` 全部展开成一个完整 YAML - 更适合想“一条链接直接导入”的场景 - 响应头继续带上订阅配额信息,方便 Stash / Clash Party 展示流量和到期 --- ## 3. 已确认的产品决策 下面这些不是临时实现细节,而是本项目当前的明确产品约定。 ### 3.1 支持多机场源选择 所有主要接口都支持: ```text ?sources=airport-a,airport-b ``` 行为: - 不传 `sources`:默认使用所有启用的源 - 传 `sources`:只使用指定源,按参数顺序处理 - 自动去重 - 禁用或不存在的源返回 404 ### 3.2 配额头策略 用户明确要求: - 只选一个机场源时:返回这个机场源的订阅配额信息 - 选多个机场源时:**只取第一个源**的配额信息 因此当前项目统一采用: - `Subscription-Userinfo` 只看 `sources` 参数中的第一个源 - 不对多个机场的总量做相加、估算或“智能合并” 这么做的原因是避免语义混乱。不同机场的总量、周期、重置时间可能完全不同,强行合成会误导客户端。 ### 3.3 GET / HEAD 都要支持 所有核心 YAML 接口同时支持: - `GET` - `HEAD` 原因: - 某些客户端会用 `HEAD` 先探测订阅信息或减少流量消耗 - 即使不返回 body,也要保留正确的响应头 当前实现策略: - `GET` 返回完整内容 + 响应头 - `HEAD` 返回空 body + 相同响应头 ### 3.4 当前版本只支持上游已输出 Clash/Mihomo YAML `proxies:` 这是一个有意为之的阶段性约束。 当前项目假定上游 URL 返回的是已经能被解析成: ```yaml proxies: - name: ... type: ... ``` 的 YAML 文档。 这样可以先接用户现有的: - sub-wrapper - sub-store - subconverter - subconverter-extended 等输出,先把整体架构跑通。 **当前还没有做:** - 原始 URI 订阅解析 - base64 Clash 订阅解析 - sing-box / Surge / Loon 原生格式输入解析 这是后续扩展点,不是当前版本目标。 --- ## 4. 本次主要做了哪些工作 ### 4.1 重新定义项目结构 把项目拆成了这些核心层: ```text sub-provider/ app/ config.py main.py models.py services/ cache.py headers.py loader.py profiles.py rules.py subscriptions.py config/ sources.yaml rules/ reject.yaml direct.yaml proxy.yaml cn-ip.yaml data/ cache/ .env.example Dockerfile docker-compose.yaml requirements.txt ``` 目的: - 避免所有逻辑都塞进 `main.py` - 让后续 Codex 能单独替换抓取层、规则层、模板层、缓存层 - 为以后增加 `regions.yaml` / `policies.yaml` / 解析器模块做准备 ### 4.2 增加了配置装载与模型层 抽离了: - App 级配置 - SourceConfig - ClientConfig - RuleConfig - RegionConfig - 配额结构 - Provider 文档结构 - SourceSnapshot 目的: - 让服务内部的处理单位明确 - 避免后续继续传裸 dict - 方便 Codex 在 builder / parser / renderer 之间改造 ### 4.3 增加了 provider 层输出能力 新增能力: - 单源 provider 输出:`/providers/{name}.yaml` - 多源 merged provider 输出:`/providers/merged.yaml?sources=...` 目的: - 支持薄壳模式的 `proxy-providers` - 支持调试和人工检查节点池 - 为后续做“按地区 provider / 按能力 provider”打基础 ### 4.4 增加了 thin client 输出能力 新增接口: - `/clients/mihomo.yaml` - `/clients/stash.yaml` thin 模式特点: - 不内联全部节点 - 继续引用 `/providers/{name}.yaml` - 继续引用 `/rules/{name}.yaml` - 只生成基础策略组 目的: - 让 Mihomo / OpenClash 保持 provider 驱动的更新机制 - 避免每次节点变化都要整份配置重拉 ### 4.5 增加了 bundle 单文件输出能力 新增接口: - `/bundle/mihomo.yaml` - `/bundle/stash.yaml` bundle 模式特点: - 服务端把所有源节点合并后写进 `proxies` - 服务端生成最终 `proxy-groups` - 服务端把规则集直接内联到 `rules` - 客户端只需要一条最终配置 URL 目的: - 满足用户“客户端直接拉一个完整 YAML”的需求 - 保留订阅头展示能力 - 方便 Clash Party / Stash 这类更偏向“配置订阅”的使用方式 ### 4.6 增加了 Subscription-Userinfo 头处理 实现了: - 从上游响应头读取配额信息 - 解析出 upload / download / total / expire - 在输出接口上重新写回 `Subscription-Userinfo` - 对多源情况只取第一个源 目的: - 让客户端继续显示订阅用量、到期时间 - 不要求客户端必须直接访问机场原始订阅 ### 4.7 补全 HEAD 支持 在以下接口上都支持 `HEAD`: - `/providers/{name}.yaml` - `/providers/merged.yaml` - `/rules/{name}.yaml` - `/clients/{client}.yaml` - `/bundle/{client}.yaml` 目的: - 兼容会先做探测的客户端 - 减少不必要的 body 传输 ### 4.8 默认生成了一套基础策略组 当前基础策略结构包括: - `☁️ 机场选择` - `♻️ 自动选择` - `🚀 手动切换` - `🇭🇰 香港自动` - `🇸🇬 新加坡自动` - `🇯🇵 日本自动` - `🇺🇸 美国自动` - `节点选择` 当前逻辑: - 每个源会生成一个“{display_name} 自动”组 - 一个混合自动组聚合所有源 - 地区组根据节点名正则匹配 - 主策略组最终统一指向 `节点选择` 目的: - 先提供最小可用的选择层 - 给后续做“provider 选择 → 地区节点选择 → 业务规则选择”留位置 --- ## 5. 当前接口约定 ### 5.1 健康检查 ```text GET /healthz ``` ### 5.2 单 provider ```text GET //providers/{name}.yaml HEAD //providers/{name}.yaml ``` 用途:返回指定机场源的 provider 文件。 ### 5.3 merged provider ```text GET //providers/merged.yaml?sources=airport-a,airport-b HEAD //providers/merged.yaml?sources=airport-a,airport-b ``` 用途:把多个源合成一个 provider 文件。 ### 5.4 rule-provider ```text GET //rules/{name}.yaml HEAD //rules/{name}.yaml ``` 用途:返回单独规则文件。 ### 5.5 thin client ```text GET //clients/mihomo.yaml?sources=... HEAD //clients/mihomo.yaml?sources=... GET //clients/stash.yaml?sources=... HEAD //clients/stash.yaml?sources=... ``` ### 5.6 bundle client ```text GET //bundle/mihomo.yaml?sources=... HEAD //bundle/mihomo.yaml?sources=... GET //bundle/stash.yaml?sources=... HEAD //bundle/stash.yaml?sources=... ``` --- ## 6. 当前默认配置文件含义 ### 6.1 `config/sources.yaml` 这是目前最核心的配置文件,已经承载了: - `public_path` - `sources` - `regions` - `rules` - `clients` 其中: #### `sources` 定义机场源: - `enabled` - `display_name` - `kind` - `url` - `prefix` - `include_regex` - `exclude_regex` 当前 `kind` 主要按 `clash_yaml` 用。 #### `regions` 定义按节点名正则筛选的地区自动组。 #### `rules` 定义规则文件与它最终映射到哪个 policy。 #### `clients` 定义每个客户端类型的: - provider 更新周期 - rule 更新周期 - test URL - 主策略组名 - 自动组名 - 手动组名 - DIRECT 策略名 - 端口和 LAN 配置(Mihomo) --- ## 7. 当前设计的主要思路 ### 7.1 内部解耦,输出再组装 这是本项目最重要的原则。 不要直接维护最终配置文件;而是: - 内部拆成源、规则、策略模板、客户端模板 - 对外按需求组合成 thin 或 bundle 这样未来无论是要做: - 新客户端 - 新地区组 - 新业务组 - 新能力标签 - 新鉴权层 都不会把整个项目拖垮。 ### 7.2 同时保留 thin 与 bundle 这不是重复劳动,而是为了满足不同客户端和不同使用习惯。 - thin 更适合 Mihomo / OpenClash - bundle 更适合想“一条链接导入”的用户 - bundle 更方便把配额头暴露给客户端 UI ### 7.3 不在当前阶段过度抽象业务组 用户后来贴出了一份更复杂的目标配置,里面已经有: - provider 选择 - 地区节点选择 - 业务策略组(Telegram / AI / YouTube / Netflix / Google / Microsoft / Apple / Game 等) - 大量规则 但本次项目中并**没有**把那整套复杂业务组全部移植进来。 本次改造的重点不是“把规则抄完”,而是先把**后端架构**做对,避免继续堆在单文件 YAML 上。 换句话说,本次完成的是: - 框架和接口层 - provider / bundle 双模式 - 配额头链路 - 基础策略组 而不是完整复刻用户那份业务规则大配置。 --- ## 8. 当前版本的局限 下面这些是明确存在、且后续值得继续做的地方。 ### 8.1 只支持 Clash/Mihomo YAML 输入 还不支持: - ss:// / vmess:// / vless:// / trojan:// 原始 URI 解析 - base64 聚合订阅解析 - sing-box 输入格式 - Surge / Loon 原生格式输入 ### 8.2 `sources.yaml` 还承担了过多职责 后续建议继续拆分: - `sources.yaml` - `regions.yaml` - `policies.yaml` - `clients.yaml` ### 8.3 业务组还没有模板化 当前只有基础组: - 机场选择 - 混合自动 - 手动切换 - 地区自动 - 节点选择 后续可以把用户想要的: - `📲 电报消息` - `💬 Ai平台` - `📹 油管视频` - `🎥 奈飞视频` - `🌍 国外媒体` - `📢 谷歌` - `Ⓜ️ 微软服务` - `🍎 苹果服务` - `🎮 游戏平台` 抽象成 `policies.yaml` + 模板生成。 ### 8.4 目前地区识别仅靠节点名正则 还没有做: - 更可靠的地区标签系统 - 能力标签(Netflix / AI / Game / Native) - 延迟或解锁能力探测 ### 8.5 没有内置鉴权 当前项目更偏“后端核心能力”,安全性仍建议依赖前置层: - Caddy / Nginx Basic Auth - 仅 Tailscale 可访问 - 或其他反代鉴权方案 --- ## 9. 后续最值得继续做的事 优先级建议如下。 ### P1:把用户目标配置里的“业务组模板”抽出来 新增: - `policies.yaml` 做法: - 把业务组可选项抽成模板 - 让规则只引用稳定业务组名 - 让机场组 / 地区组 / 自动组作为底层构件 目标: 让项目真正实现“provider 选择 → 节点选择 → 业务规则指向”。 ### P1:把地区配置独立出来 新增: - `regions.yaml` 目标: - 减少 `sources.yaml` 复杂度 - 地区过滤可以单独维护 ### P1:支持原始订阅解析 新增: - parser 层 目标: - 直接吃机场原始 URI / base64 订阅 - 降低对外部转换器的依赖 ### P2:支持能力标签 例如: - `netflix: true` - `ai: true` - `game: true` - `native: true` 目标: - 自动生成 `🎥 奈飞节点`、`💬 AI可用节点` 之类的能力池 ### P2:缓存和探测增强 目标: - provider 拉取加 TTL / ETag / 条件请求 - 节点能力探测可选化 - 避免每次请求都全量重拉上游 ### P3:更强的模板体系 目标: - 区分 Mihomo / Stash / OpenClash 细节 - 更好地控制 bundle 与 thin 差异 --- ## 10. 给 Codex 的工作建议 如果 Codex 要继续接手,建议按下面顺序继续,不要直接推翻当前结构。 ### 建议 1 先保留当前 `services/` 分层,不要把逻辑重新打回 `main.py`。 ### 建议 2 优先做 `policies.yaml`,把用户贴出来的复杂业务组迁进模板体系。 ### 建议 3 不要急着做“多机场流量合并显示”,继续坚持“只取第一个源”的策略,除非产品需求明确变更。 ### 建议 4 原始订阅解析应作为单独模块实现,不要污染当前 provider / profile builder。 ### 建议 5 如果将来要支持更多客户端,优先抽象: - 输入解析层 - 中间节点模型 - 输出模板层 而不是在 builder 里堆条件分支。 --- ## 11. 一句话总结 这个项目的本质不是“再写一个 subconverter”,而是: > **把多机场源、规则、策略组和客户端输出方式解耦维护,再按客户端需要动态组装成 thin 配置或 bundle 配置,并把第一个源的订阅配额头继续透传给客户端。** 这就是当前这版优化的核心目标,也是后续继续演进时不应该偏离的方向。