# 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. 目标架构 推荐演进成如下结构: ```text 请求 -> API 路由 -> Profile 解析 -> Source 选择 -> Source 拉取 / 缓存 -> 节点解析 / 标准化 / 处理 -> 策略组装配 -> 规则模块解析 -> 完整 YAML 渲染 -> 结果缓存 / 快照 -> 返回响应头与 YAML ``` 配置来源则逐步从: ```text sources.yaml ``` 演进为: ```text 数据库配置 + 本地规则资产文件 + 少量默认 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. 把“配置加载”和“配置解释”职责从“服务逻辑”中剥离 建议目录演进: ```text 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 ``` 验收标准: * 对外接口路径和行为保持不变 * `bundle` 与 `thin` 输出内容结构基本一致 * 已不再依赖单个巨型 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 #### 内部模型建议 不要把节点模型收得太死。建议: ```python 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` 的重构最稳路线是: > 先做配置解耦,再做数据库配置层,再做渲染流水线固化,最后补管理页面和快照系统。 这样每一阶段都能保留一个可运行版本,也最符合当前项目已经有代码基础的现实情况。