687 lines
14 KiB
Markdown
687 lines
14 KiB
Markdown
# 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` 的重构最稳路线是:
|
||
|
||
> 先做配置解耦,再做数据库配置层,再做渲染流水线固化,最后补管理页面和快照系统。
|
||
|
||
这样每一阶段都能保留一个可运行版本,也最符合当前项目已经有代码基础的现实情况。
|