Files
sub-provider/sub-provider-project-context.md
riglen 09a9faa1be opt
2026-03-31 16:39:23 +08:00

653 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 /<PUBLIC_PATH>/providers/{name}.yaml
HEAD /<PUBLIC_PATH>/providers/{name}.yaml
```
用途:返回指定机场源的 provider 文件。
### 5.3 merged provider
```text
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
```text
GET /<PUBLIC_PATH>/rules/{name}.yaml
HEAD /<PUBLIC_PATH>/rules/{name}.yaml
```
用途:返回单独规则文件。
### 5.5 thin client
```text
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
```text
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_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 配置,并把第一个源的订阅配额头继续透传给客户端。**
这就是当前这版优化的核心目标,也是后续继续演进时不应该偏离的方向。