14 KiB
sub-provider 项目上下文说明
本文档用于给后续接手的 Codex / 开发者快速建立上下文,理解这个项目为什么存在、当前已经做了什么、设计取舍是什么、下一步应该往哪里继续。
1. 项目背景
用户当前的代理使用场景比较复杂:
- Windows 主力客户端:Mihomo Party / Clash Party
- iOS 客户端:Stash
- 路由侧:OpenClash
- 上游机场源不止一个,其中至少有一个已经变成了 AnyTLS
- 过去主要依赖
subconverter/subconverter-extended/ ACLSSR 模板体系做转换
用户遇到的问题是:
- 上游协议变复杂后,传统模板式转换越来越脆弱。
- 想统一管理多个机场源,但不想把所有逻辑继续绑死在单个大 YAML 里。
- 希望同时兼容两种使用习惯:
- 客户端直接拉一个完整配置文件
- 客户端拉一个轻量壳子,然后继续通过 provider 自动更新节点和规则
- 希望保留订阅使用量 / 到期时间展示能力,即把上游订阅返回的
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 支持多机场源选择
所有主要接口都支持:
?sources=airport-a,airport-b
行为:
- 不传
sources:默认使用所有启用的源 - 传
sources:只使用指定源,按参数顺序处理 - 自动去重
- 禁用或不存在的源返回 404
3.2 配额头策略
用户明确要求:
- 只选一个机场源时:返回这个机场源的订阅配额信息
- 选多个机场源时:只取第一个源的配额信息
因此当前项目统一采用:
Subscription-Userinfo只看sources参数中的第一个源- 不对多个机场的总量做相加、估算或“智能合并”
这么做的原因是避免语义混乱。不同机场的总量、周期、重置时间可能完全不同,强行合成会误导客户端。
3.3 GET / HEAD 都要支持
所有核心 YAML 接口同时支持:
GETHEAD
原因:
- 某些客户端会用
HEAD先探测订阅信息或减少流量消耗 - 即使不返回 body,也要保留正确的响应头
当前实现策略:
GET返回完整内容 + 响应头HEAD返回空 body + 相同响应头
3.4 当前版本只支持上游已输出 Clash/Mihomo YAML proxies:
这是一个有意为之的阶段性约束。
当前项目假定上游 URL 返回的是已经能被解析成:
proxies:
- name: ...
type: ...
的 YAML 文档。
这样可以先接用户现有的:
- sub-wrapper
- sub-store
- subconverter
- subconverter-extended
等输出,先把整体架构跑通。
当前还没有做:
- 原始 URI 订阅解析
- base64 Clash 订阅解析
- sing-box / Surge / Loon 原生格式输入解析
这是后续扩展点,不是当前版本目标。
4. 本次主要做了哪些工作
4.1 重新定义项目结构
把项目拆成了这些核心层:
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 健康检查
GET /healthz
5.2 单 provider
GET /<PUBLIC_PATH>/providers/{name}.yaml
HEAD /<PUBLIC_PATH>/providers/{name}.yaml
用途:返回指定机场源的 provider 文件。
5.3 merged provider
GET /<PUBLIC_PATH>/providers/merged.yaml?sources=airport-a,airport-b
HEAD /<PUBLIC_PATH>/providers/merged.yaml?sources=airport-a,airport-b
用途:把多个源合成一个 provider 文件。
5.4 rule-provider
GET /<PUBLIC_PATH>/rules/{name}.yaml
HEAD /<PUBLIC_PATH>/rules/{name}.yaml
用途:返回单独规则文件。
5.5 thin client
GET /<PUBLIC_PATH>/clients/mihomo.yaml?sources=...
HEAD /<PUBLIC_PATH>/clients/mihomo.yaml?sources=...
GET /<PUBLIC_PATH>/clients/stash.yaml?sources=...
HEAD /<PUBLIC_PATH>/clients/stash.yaml?sources=...
5.6 bundle client
GET /<PUBLIC_PATH>/bundle/mihomo.yaml?sources=...
HEAD /<PUBLIC_PATH>/bundle/mihomo.yaml?sources=...
GET /<PUBLIC_PATH>/bundle/stash.yaml?sources=...
HEAD /<PUBLIC_PATH>/bundle/stash.yaml?sources=...
6. 当前默认配置文件含义
6.1 config/sources.yaml
这是目前最核心的配置文件,已经承载了:
public_pathsourcesregionsrulesclients
其中:
sources
定义机场源:
enableddisplay_namekindurlprefixinclude_regexexclude_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.yamlregions.yamlpolicies.yamlclients.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: trueai: truegame: truenative: 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 配置,并把第一个源的订阅配额头继续透传给客户端。
这就是当前这版优化的核心目标,也是后续继续演进时不应该偏离的方向。