Files
sub-provider/docs/分阶段重构方案.md
2026-04-20 10:35:03 +08:00

14 KiB
Raw Blame History

sub-provider 分阶段重构方案

1. 文档目标

这份文档不是重新定义“最终想做成什么”,而是基于当前 sub-provider 已有实现,给出一版可逐步落地、每阶段都能交付可运行结果的重构路径。

当前项目已经具备以下基础能力:

  • 读取 config/sources.yaml
  • 拉取和缓存上游订阅
  • 解析 Clash YAML / base64 URI / 明文 URI 订阅
  • 合并节点并生成 bundle YAML
  • 生成 thin client 配置
  • 加载本地规则文件并拼接 rules
  • 透传第一个源的 Subscription-Userinfo

因此重构的目标不是“从零搭系统”,而是把现有静态配置驱动的生成器,逐步改造成:

  • 内部模块化更清晰
  • 配置存储可演进到数据库
  • 对外仍以完整 YAML 为主
  • 保留现有已验证能力
  • 后续可平滑加管理页和快照系统

2. 总体原则

原则 1对外接口尽量稳定

在重构前几阶段,尽量不打断现有输出能力:

  • /bundle/{client}.yaml
  • /clients/{client}.yaml
  • /providers/{name}.yaml
  • /providers/merged.yaml

即使最终主路线偏向 bundle也不建议在早期重构中直接删除 thin/provider 能力。

原则 2先拆“配置来源”再拆“表现层”

当前项目的核心问题不是生成逻辑完全不可用,而是配置全部堆在 sources.yaml 里。
优先级应是:

  1. 把配置从单一 YAML 拆到更清晰的数据结构
  2. 再引入数据库承载配置
  3. 最后再补管理页面

不要一上来把大量时间花在页面上。

原则 3规则正文继续文件化

规则文件仍然保存在项目目录中,继续使用 Git 管理。
数据库只存:

  • 规则模块元数据
  • 规则启用关系
  • 规则顺序
  • 规则绑定到哪个策略组

不把 ACL4SSR 规则逐条拆进数据库。

原则 4保留动态选源能力

当前项目支持通过 ?sources= 在请求级动态组合源,这个能力实际很有价值。
重构后不建议退化成只能在 profile 中静态绑定源。

建议模型上同时支持:

  • profile 默认源集合
  • 请求级 sources override

3. 对现有项目的基线判断

3.1 现状优点

当前代码结构虽然还偏轻量,但核心职责已经初步分开:

  • subscriptions.py 负责源抓取、解析、节点转换
  • profiles.py 负责 thin / bundle 配置组装
  • rules.py 负责规则文件加载和规则输出
  • bundle_cache.py / fetch_cache.py 负责磁盘缓存

这意味着重构时应该优先“抽象和替换内部数据来源”,而不是推倒服务层。

3.2 现状主要问题

当前主要限制有:

  • sources.yaml 同时承载源配置、地区分组、业务策略组、规则绑定、客户端配置
  • 配置模型偏静态,不利于面板管理
  • bundle 缓存仍是 TTL 模式,不是内容指纹模式
  • 没有配置快照、生成快照、审计能力
  • 当前没有数据库层,也没有迁移体系

3.3 结论

最稳的路线不是“大重写”,而是:

  1. 保持现有生成骨架
  2. 把配置与渲染逻辑进一步解耦
  3. 再把配置落进数据库
  4. 最后加面板和快照

4. 目标架构

推荐演进成如下结构:

请求
  -> API 路由
  -> Profile 解析
  -> Source 选择
  -> Source 拉取 / 缓存
  -> 节点解析 / 标准化 / 处理
  -> 策略组装配
  -> 规则模块解析
  -> 完整 YAML 渲染
  -> 结果缓存 / 快照
  -> 返回响应头与 YAML

配置来源则逐步从:

sources.yaml

演进为:

数据库配置 + 本地规则资产文件 + 少量默认 YAML 模板

5. 分阶段路线

建议拆成五个阶段,每个阶段结束都能保留一个稳定可运行版本。

阶段 0重构准备与基线冻结

目标:

  • 不改行为,先明确现有能力边界
  • 为后续重构建立可回归的基线

本阶段任务:

  1. 盘点当前公开接口、查询参数、响应头语义
  2. 记录当前 sources.yaml 中各配置块的职责
  3. 为核心生成路径补最小测试
  4. 输出一份“当前行为基线”文档

验收标准:

  • 至少覆盖以下场景:
    • 单源 provider
    • 多源 merged provider
    • bundle 输出
    • thin 输出
    • 第一个源配额头透传
  • 有一组固定输入可验证当前输出结构不被意外破坏

建议产物:

  • tests/ 下最小回归测试
  • docs/当前行为基线.md

说明:

这一阶段很重要。没有基线,后面数据库化后很难判断是“重构带来的结构变化”,还是“偷偷改了行为”。


阶段 1配置解耦但仍以 YAML 为配置源

目标:

  • 先把“一个巨大的 sources.yaml”拆成多个清晰模块
  • 暂时不引入数据库
  • 不改变线上运行方式

本阶段任务:

  1. 拆分当前 sources.yaml
    • config/sources.yaml
    • config/clients.yaml
    • config/rule-bindings.yaml
    • config/policy-groups.yaml
    • config/regions.yaml
  2. 新增统一配置加载层,把多个 YAML 聚合成当前运行所需的内部配置对象
  3. 把当前 AppConfig 进一步拆开,减少“大一统模型”
  4. main.py 不再直接依赖单个全局 app_config
  5. 把“配置加载”和“配置解释”职责从“服务逻辑”中剥离

建议目录演进:

app/
  config.py
  main.py
  models/
    source.py
    client.py
    profile.py
    rule.py
    group.py
  services/
    config_loader.py
    subscriptions.py
    profiles.py
    rules.py

验收标准:

  • 对外接口路径和行为保持不变
  • bundlethin 输出内容结构基本一致
  • 已不再依赖单个巨型 YAML 文件

这一阶段收益:

  • 先把系统结构理顺
  • 后续接数据库时,只需要替换配置仓储层,不需要大改生成器本身

阶段 2引入数据库配置层保留 YAML 兼容导入

目标:

  • 让配置从静态文件迁移到数据库
  • 但仍支持从 YAML 导入初始数据

本阶段任务:

  1. 引入 SQLAlchemy 与 Alembic
  2. 增加 DATABASE_URL
  3. 建立第一批核心表
  4. 实现配置仓储层
  5. 提供 YAML 初始化导入脚本

推荐第一批表:

  • sources
  • profiles
  • profile_sources
  • rule_modules
  • profile_rule_modules
  • policy_groups
  • profile_overrides

推荐暂不引入的表:

  • generated_artifacts
  • sync_logs
  • rule_module_versions

原因:

先把“当前运行配置”迁过去,比一开始就把快照、审计、版本全部做全更稳。

推荐字段设计

sources

  • id
  • key
  • name
  • enabled
  • kind
  • url
  • display_name
  • headers_json
  • include_regex
  • exclude_regex
  • prefix
  • suffix
  • cache_ttl_seconds
  • created_at
  • updated_at

profiles

  • id
  • key
  • name
  • client_type
  • description
  • enabled
  • allow_lan
  • ipv6
  • mixed_port
  • socks_port
  • mode
  • log_level
  • main_policy
  • source_policy
  • mixed_auto_policy
  • manual_policy
  • direct_policy
  • test_url
  • test_interval
  • provider_interval
  • rule_interval
  • created_at
  • updated_at

profile_sources

  • id
  • profile_id
  • source_id
  • order_index
  • enabled

rule_modules

  • id
  • key
  • name
  • description
  • file_path
  • behavior
  • format
  • default_policy
  • default_no_resolve
  • default_payload_json
  • category
  • created_at
  • updated_at

profile_rule_modules

  • id
  • profile_id
  • rule_module_id
  • enabled
  • order_index
  • policy_override
  • payload_override_json
  • no_resolve_override

policy_groups

  • id
  • profile_id
  • name
  • group_kind
  • type
  • order_index
  • filter_regex
  • proxies_json
  • url
  • interval
  • tolerance
  • enabled

这里的 group_kind 建议至少支持:

  • static
  • filter
  • generated

这比单纯用一个 config_json 更适合当前项目,因为现有组装里同时存在静态组和按 regex 选节点的组。

仓储层建议

新增仓储抽象,例如:

  • SourceRepository
  • ProfileRepository
  • RuleModuleRepository
  • PolicyGroupRepository

让生成服务只依赖仓储接口,而不是依赖数据库实现细节。

验收标准:

  • 使用数据库也能生成与阶段 1 基本一致的 bundle/thin 配置
  • 可以从原 YAML 导入初始配置
  • 切换配置来源时,渲染层无需大改

阶段 3渲染流水线模块化与输出主路线收敛

目标:

  • 把“源处理、规则处理、组装渲染”彻底做成稳定流水线
  • 明确 bundle 是主输出路线
  • thin/provider 进入兼容保留状态

本阶段任务:

  1. 拆分服务层职责:
    • source_fetcher.py
    • source_parser.py
    • proxy_processor.py
    • policy_group_builder.py
    • rule_resolver.py
    • profile_renderer.py
  2. 引入统一内部节点模型
  3. 引入统一内部“已解析 profile”模型
  4. 将当前 build_bundle_profile() 的逻辑拆成多步处理
  5. 清理当前 token 展开逻辑,明确保留哪些模板 token

内部模型建议

不要把节点模型收得太死。建议:

class ProxyNode(BaseModel):
    name: str
    type: str
    server: str | None = None
    port: int | None = None
    udp: bool = True
    tags: list[str] = Field(default_factory=list)
    attrs: dict[str, Any] = Field(default_factory=dict)

其中:

  • 通用字段单独保留
  • 协议特有字段放 attrs
  • 最终输出时再按协议合并回 YAML 结构

这样比单纯的 raw 更可控。

规则系统建议

这一阶段不要急着把所有规则文件改成 {{ target_group }} 模板格式。
先保留当前模式:

  • 文件内容仍是 payload
  • policy 在 profile 绑定关系上决定

原因:

  • 当前 thin 模式的 rule-providers 仍依赖 payload 文件输出
  • 先保留兼容性,后面再决定是否升级规则文件模板化

验收标准:

  • bundle 渲染主路径清晰可测
  • 服务层之间依赖方向清楚
  • 配置仓储和渲染器分离

阶段 4管理接口与最小面板

目标:

  • 提供最小配置管理能力
  • 不做复杂前后端分离

本阶段任务:

  1. 增加管理 API
    • source CRUD
    • profile CRUD
    • rule module 查看与 profile 绑定管理
    • policy group 查看与编辑
  2. 增加预览接口
  3. 增加最小页面:
    • sources
    • profiles
    • rule bindings
    • preview
  4. 提供 profile 级 bundle 下载入口

建议接口:

  • GET /api/sources
  • POST /api/sources
  • PUT /api/sources/{id}
  • GET /api/profiles
  • POST /api/profiles
  • PUT /api/profiles/{id}
  • GET /api/profiles/{id}/rules
  • PUT /api/profiles/{id}/rules
  • GET /api/profiles/{id}/groups
  • PUT /api/profiles/{id}/groups
  • POST /api/profiles/{id}/preview
  • GET /profiles/{key}/bundle.yaml

面板技术建议:

  • Jinja2
  • HTMX 或最少量原生 JS

不建议此时做:

  • React/Vue 前后端分离
  • 复杂权限系统
  • 在线全文规则编辑器

验收标准:

  • 不改代码即可新增一个 source
  • 不改 YAML 文件即可调整 profile 规则顺序
  • 页面可直接预览 bundle 输出

阶段 5快照、缓存升级与可追踪性

目标:

  • 从“能生成”升级到“可追踪、可复现、可排查”

本阶段任务:

  1. 增加 generated_artifacts
  2. 增加内容指纹缓存
  3. 增加源同步状态记录
  4. 增加简单 diff 能力
  5. 增加错误展示

推荐新增表:

generated_artifacts

  • id
  • profile_id
  • request_sources_json
  • content
  • content_hash
  • source_hash
  • rules_hash
  • profile_hash
  • headers_json
  • generated_at

source_sync_logs

  • id
  • source_id
  • status
  • error_message
  • response_headers_json
  • content_hash
  • created_at

缓存策略建议

当前项目 bundle 缓存是 TTL 模式。后续应逐步改为:

  • fetch cache: TTL
  • parsed snapshot cache: 源内容 hash
  • bundle cache: source_hash + rules_hash + profile_hash + request_sources

这样才能真正做到:

  • 配置没变就稳定复用
  • 配置变了就精准失效

验收标准:

  • 能查看某个 profile 最近几次生成记录
  • 能知道本次 bundle 为什么重新生成
  • 能追踪某个源最近一次同步是否失败

6. 推荐实施顺序

如果按投入产出比排序,建议实际开发顺序如下:

  1. 阶段 0
  2. 阶段 1
  3. 阶段 2
  4. 阶段 3
  5. 阶段 4
  6. 阶段 5

其中真正的“第一版可交付里程碑”建议定在阶段 2 结束时。

因为到了阶段 2已经具备

  • 数据库存储配置
  • 可继续生成完整 YAML
  • 配置不再绑死在单文件 YAML 中

这时即使还没有面板,系统内核已经完成了最关键升级。


7. 每阶段风险点

阶段 1 风险

风险:

  • 拆 YAML 时容易出现字段兼容问题

控制方式:

  • 先保留兼容加载层
  • 老配置格式在一段时间内继续可读

阶段 2 风险

风险:

  • 数据建模过度,导致表过多、实现过重

控制方式:

  • 第一批只建运行必需表
  • 快照与日志延后

阶段 3 风险

风险:

  • 过早重写规则系统,导致 thin 模式兼容性下降

控制方式:

  • 先保留 payload 模式
  • 规则模板化放后续增量处理

阶段 4 风险

风险:

  • 过早投入页面开发,拖慢主流程

控制方式:

  • 先 API 后页面
  • 页面只做最小可用

阶段 5 风险

风险:

  • 快照和缓存逻辑做得太复杂,维护成本上升

控制方式:

  • 先做内容哈希与生成记录
  • diff 与审计能力逐步补

8. 明确不建议的做法

  1. 不建议一开始就删除当前 thin/provider 能力
  2. 不建议一开始就把所有规则文件模板化
  3. 不建议把 profile 的源集合只做成一个 source_ids_json
  4. 不建议把策略组全塞进一个不透明 config_json
  5. 不建议先做复杂前端再补业务内核
  6. 不建议先做快照系统再做数据库配置层

9. 建议的第一批实际改动

如果下一步开始真正动代码,建议先做这几件事:

  1. 新建阶段 0 的测试和基线文档
  2. 拆分 sources.yaml,完成阶段 1
  3. 引入 SQLAlchemy/Alembic 和最小表结构
  4. 增加 YAML -> DB 导入脚本
  5. 让 bundle 渲染从数据库读取 profile / source / rules 配置

这五步完成后,再去做页面,整体节奏会稳很多。


10. 一句话结论

sub-provider 的重构最稳路线是:

先做配置解耦,再做数据库配置层,再做渲染流水线固化,最后补管理页面和快照系统。

这样每一阶段都能保留一个可运行版本,也最符合当前项目已经有代码基础的现实情况。