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

14 KiB
Raw Blame History

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

特点:

  • 服务端把 proxiesproxy-groupsrules 全部展开成一个完整 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 接口同时支持:

  • GET
  • HEAD

原因:

  • 某些客户端会用 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_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 配置,并把第一个源的订阅配额头继续透传给客户端。

这就是当前这版优化的核心目标,也是后续继续演进时不应该偏离的方向。