rule重构

This commit is contained in:
riglen
2026-04-20 10:35:03 +08:00
parent f449aebbc7
commit 1cbf080e1e
11 changed files with 2199 additions and 485 deletions

View File

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