14 KiB
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 里。
优先级应是:
- 把配置从单一 YAML 拆到更清晰的数据结构
- 再引入数据库承载配置
- 最后再补管理页面
不要一上来把大量时间花在页面上。
原则 3:规则正文继续文件化
规则文件仍然保存在项目目录中,继续使用 Git 管理。
数据库只存:
- 规则模块元数据
- 规则启用关系
- 规则顺序
- 规则绑定到哪个策略组
不把 ACL4SSR 规则逐条拆进数据库。
原则 4:保留动态选源能力
当前项目支持通过 ?sources= 在请求级动态组合源,这个能力实际很有价值。
重构后不建议退化成只能在 profile 中静态绑定源。
建议模型上同时支持:
- profile 默认源集合
- 请求级
sourcesoverride
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 结论
最稳的路线不是“大重写”,而是:
- 保持现有生成骨架
- 把配置与渲染逻辑进一步解耦
- 再把配置落进数据库
- 最后加面板和快照
4. 目标架构
推荐演进成如下结构:
请求
-> API 路由
-> Profile 解析
-> Source 选择
-> Source 拉取 / 缓存
-> 节点解析 / 标准化 / 处理
-> 策略组装配
-> 规则模块解析
-> 完整 YAML 渲染
-> 结果缓存 / 快照
-> 返回响应头与 YAML
配置来源则逐步从:
sources.yaml
演进为:
数据库配置 + 本地规则资产文件 + 少量默认 YAML 模板
5. 分阶段路线
建议拆成五个阶段,每个阶段结束都能保留一个稳定可运行版本。
阶段 0:重构准备与基线冻结
目标:
- 不改行为,先明确现有能力边界
- 为后续重构建立可回归的基线
本阶段任务:
- 盘点当前公开接口、查询参数、响应头语义
- 记录当前
sources.yaml中各配置块的职责 - 为核心生成路径补最小测试
- 输出一份“当前行为基线”文档
验收标准:
- 至少覆盖以下场景:
- 单源 provider
- 多源 merged provider
- bundle 输出
- thin 输出
- 第一个源配额头透传
- 有一组固定输入可验证当前输出结构不被意外破坏
建议产物:
tests/下最小回归测试docs/当前行为基线.md
说明:
这一阶段很重要。没有基线,后面数据库化后很难判断是“重构带来的结构变化”,还是“偷偷改了行为”。
阶段 1:配置解耦,但仍以 YAML 为配置源
目标:
- 先把“一个巨大的
sources.yaml”拆成多个清晰模块 - 暂时不引入数据库
- 不改变线上运行方式
本阶段任务:
- 拆分当前
sources.yaml:config/sources.yamlconfig/clients.yamlconfig/rule-bindings.yamlconfig/policy-groups.yamlconfig/regions.yaml
- 新增统一配置加载层,把多个 YAML 聚合成当前运行所需的内部配置对象
- 把当前
AppConfig进一步拆开,减少“大一统模型” - 让
main.py不再直接依赖单个全局app_config - 把“配置加载”和“配置解释”职责从“服务逻辑”中剥离
建议目录演进:
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 导入初始数据
本阶段任务:
- 引入 SQLAlchemy 与 Alembic
- 增加
DATABASE_URL - 建立第一批核心表
- 实现配置仓储层
- 提供 YAML 初始化导入脚本
推荐第一批表:
sourcesprofilesprofile_sourcesrule_modulesprofile_rule_modulespolicy_groupsprofile_overrides
推荐暂不引入的表:
generated_artifactssync_logsrule_module_versions
原因:
先把“当前运行配置”迁过去,比一开始就把快照、审计、版本全部做全更稳。
推荐字段设计
sources
idkeynameenabledkindurldisplay_nameheaders_jsoninclude_regexexclude_regexprefixsuffixcache_ttl_secondscreated_atupdated_at
profiles
idkeynameclient_typedescriptionenabledallow_lanipv6mixed_portsocks_portmodelog_levelmain_policysource_policymixed_auto_policymanual_policydirect_policytest_urltest_intervalprovider_intervalrule_intervalcreated_atupdated_at
profile_sources
idprofile_idsource_idorder_indexenabled
rule_modules
idkeynamedescriptionfile_pathbehaviorformatdefault_policydefault_no_resolvedefault_payload_jsoncategorycreated_atupdated_at
profile_rule_modules
idprofile_idrule_module_idenabledorder_indexpolicy_overridepayload_override_jsonno_resolve_override
policy_groups
idprofile_idnamegroup_kindtypeorder_indexfilter_regexproxies_jsonurlintervaltoleranceenabled
这里的 group_kind 建议至少支持:
staticfiltergenerated
这比单纯用一个 config_json 更适合当前项目,因为现有组装里同时存在静态组和按 regex 选节点的组。
仓储层建议
新增仓储抽象,例如:
SourceRepositoryProfileRepositoryRuleModuleRepositoryPolicyGroupRepository
让生成服务只依赖仓储接口,而不是依赖数据库实现细节。
验收标准:
- 使用数据库也能生成与阶段 1 基本一致的 bundle/thin 配置
- 可以从原 YAML 导入初始配置
- 切换配置来源时,渲染层无需大改
阶段 3:渲染流水线模块化与输出主路线收敛
目标:
- 把“源处理、规则处理、组装渲染”彻底做成稳定流水线
- 明确 bundle 是主输出路线
- thin/provider 进入兼容保留状态
本阶段任务:
- 拆分服务层职责:
source_fetcher.pysource_parser.pyproxy_processor.pypolicy_group_builder.pyrule_resolver.pyprofile_renderer.py
- 引入统一内部节点模型
- 引入统一内部“已解析 profile”模型
- 将当前
build_bundle_profile()的逻辑拆成多步处理 - 清理当前 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:管理接口与最小面板
目标:
- 提供最小配置管理能力
- 不做复杂前后端分离
本阶段任务:
- 增加管理 API:
- source CRUD
- profile CRUD
- rule module 查看与 profile 绑定管理
- policy group 查看与编辑
- 增加预览接口
- 增加最小页面:
- sources
- profiles
- rule bindings
- preview
- 提供 profile 级 bundle 下载入口
建议接口:
GET /api/sourcesPOST /api/sourcesPUT /api/sources/{id}GET /api/profilesPOST /api/profilesPUT /api/profiles/{id}GET /api/profiles/{id}/rulesPUT /api/profiles/{id}/rulesGET /api/profiles/{id}/groupsPUT /api/profiles/{id}/groupsPOST /api/profiles/{id}/previewGET /profiles/{key}/bundle.yaml
面板技术建议:
- Jinja2
- HTMX 或最少量原生 JS
不建议此时做:
- React/Vue 前后端分离
- 复杂权限系统
- 在线全文规则编辑器
验收标准:
- 不改代码即可新增一个 source
- 不改 YAML 文件即可调整 profile 规则顺序
- 页面可直接预览 bundle 输出
阶段 5:快照、缓存升级与可追踪性
目标:
- 从“能生成”升级到“可追踪、可复现、可排查”
本阶段任务:
- 增加
generated_artifacts - 增加内容指纹缓存
- 增加源同步状态记录
- 增加简单 diff 能力
- 增加错误展示
推荐新增表:
generated_artifacts
idprofile_idrequest_sources_jsoncontentcontent_hashsource_hashrules_hashprofile_hashheaders_jsongenerated_at
source_sync_logs
idsource_idstatuserror_messageresponse_headers_jsoncontent_hashcreated_at
缓存策略建议
当前项目 bundle 缓存是 TTL 模式。后续应逐步改为:
- fetch cache: TTL
- parsed snapshot cache: 源内容 hash
- bundle cache:
source_hash + rules_hash + profile_hash + request_sources
这样才能真正做到:
- 配置没变就稳定复用
- 配置变了就精准失效
验收标准:
- 能查看某个 profile 最近几次生成记录
- 能知道本次 bundle 为什么重新生成
- 能追踪某个源最近一次同步是否失败
6. 推荐实施顺序
如果按投入产出比排序,建议实际开发顺序如下:
- 阶段 0
- 阶段 1
- 阶段 2
- 阶段 3
- 阶段 4
- 阶段 5
其中真正的“第一版可交付里程碑”建议定在阶段 2 结束时。
因为到了阶段 2,已经具备:
- 数据库存储配置
- 可继续生成完整 YAML
- 配置不再绑死在单文件 YAML 中
这时即使还没有面板,系统内核已经完成了最关键升级。
7. 每阶段风险点
阶段 1 风险
风险:
- 拆 YAML 时容易出现字段兼容问题
控制方式:
- 先保留兼容加载层
- 老配置格式在一段时间内继续可读
阶段 2 风险
风险:
- 数据建模过度,导致表过多、实现过重
控制方式:
- 第一批只建运行必需表
- 快照与日志延后
阶段 3 风险
风险:
- 过早重写规则系统,导致 thin 模式兼容性下降
控制方式:
- 先保留 payload 模式
- 规则模板化放后续增量处理
阶段 4 风险
风险:
- 过早投入页面开发,拖慢主流程
控制方式:
- 先 API 后页面
- 页面只做最小可用
阶段 5 风险
风险:
- 快照和缓存逻辑做得太复杂,维护成本上升
控制方式:
- 先做内容哈希与生成记录
- diff 与审计能力逐步补
8. 明确不建议的做法
- 不建议一开始就删除当前 thin/provider 能力
- 不建议一开始就把所有规则文件模板化
- 不建议把 profile 的源集合只做成一个
source_ids_json - 不建议把策略组全塞进一个不透明
config_json - 不建议先做复杂前端再补业务内核
- 不建议先做快照系统再做数据库配置层
9. 建议的第一批实际改动
如果下一步开始真正动代码,建议先做这几件事:
- 新建阶段 0 的测试和基线文档
- 拆分
sources.yaml,完成阶段 1 - 引入 SQLAlchemy/Alembic 和最小表结构
- 增加 YAML -> DB 导入脚本
- 让 bundle 渲染从数据库读取 profile / source / rules 配置
这五步完成后,再去做页面,整体节奏会稳很多。
10. 一句话结论
sub-provider 的重构最稳路线是:
先做配置解耦,再做数据库配置层,再做渲染流水线固化,最后补管理页面和快照系统。
这样每一阶段都能保留一个可运行版本,也最符合当前项目已经有代码基础的现实情况。