Files
sub-store/docs/backend-dev-plan.md
T

43 KiB
Raw Blame History

Sub-Store Go 后端功能开发计划

技术栈:Go 1.23+ / Fiber v3 / Cobra / Viper / Logrus / SQLite 参考原项目:ref/sub-store-cloudflare/cloudflare/ (TypeScript, 5404 行) 核心原则:优先使用成熟开源库,不自造轮子


1. 技术选型

1.1 核心框架

用途 版本 理由
HTTP 框架 github.com/gofiber/fiber/v3 v3.x 用户指定
CLI 启动 github.com/spf13/cobra v1.x 用户指定
配置管理 github.com/spf13/viper v1.x 用户指定
日志 github.com/sirupsen/logrus v1.x 用户指定
SQLite 驱动 modernc.org/sqlite latest 纯 Go,无 CGO,跨平台编译
ORM/查询 github.com/jmoiron/sqlx latest 轻量,sql 行映射,不引入 GORM 重量级
迁移 github.com/pressly/goose/v3 latest 成熟 SQL 迁移工具,//go:embed 嵌入
中文排序 golang.org/x/text latest collate + language.SimplifiedChinese 拼音排序

1.2 功能库

用途 理由
YAML 序列化 gopkg.in/yaml.v3 mihomo 配置和模板操作
HTTP 客户端 github.com/go-resty/resty/v2 重试/超时/流式读取,比 net/http 更简洁
UUID github.com/google/uuid grant/recycle ID 生成
验证 github.com/go-playground/validator/v10 请求体校验
正则 regexp (标准库 RE2) 注意:RE2 不支持反向引用/lookahead,原项目部分正则需适配
Base64 encoding/base64 (标准库)
Crypto crypto/subtle, crypto/sha256, crypto/rand (标准库) token 校验、hash、随机
URL 解析 net/url (标准库) URI 协议解析
JSON encoding/json (标准库)
信号处理 os/signal (标准库) 优雅关闭
嵌入静态资源 embed (标准库) 前端 SPA

1.3 原项目依赖对照

原项目 (TS) Go 替代 说明
Hono Fiber v3 路由 + 中间件
D1 (SQLite) modernc.org/sqlite + sqlx 直接 SQLite
caches.default (Cache API) SQLite source_cache 零额外依赖,重启不丢,WAL 读写不互斥
crypto.subtle.timingSafeEqual crypto/subtle.ConstantTimeCompare
c.req.json() Fiber c.Bind() / c.Body()
ExecutionCtx.waitUntil go func() + context goroutine 异步
env.DB.batch() sqlx BeginTx() + 多语句 事务
atob / btoa encoding/base64
yaml npm 包 gopkg.in/yaml.v3
json5 npm 包 encoding/json + 宽容解析 JSON5 场景仅用于代理节点数组解析,可用标准 JSON + 回退
structuredClone 深拷贝工具函数 reflect 或手写

2. 项目结构

sub-store/
├── main.go                         # 入口,调用 cmd
├── cmd/
│   ├── root.go                     # cobra root command
│   ├── serve.go                    # sub-store serve (启动 HTTP 服务,启动时自动 migrate)
│   └── version.go                  # sub-store version
├── internal/
│   ├── config/
│   │   └── config.go               # viper 配置加载,结构体定义
│   ├── model/
│   │   ├── types.go                # 核心类型:ProxyNode, FilterRule, SourceRecord, ...
│   │   ├── source.go               # SubscriptionSource
│   │   ├── collection.go           # SubscriptionCollection
│   │   ├── template.go             # RoutingTemplate, RoutingTemplateConfig
│   │   ├── target.go               # SubscriptionTarget 常量与别名
│   │   └── response.go             # API 响应封装
│   ├── database/
│   │   ├── db.go                   # SQLite 连接初始化
│   │   ├── migrations/
│   │   │   ├── 0001_initial.sql
│   │   │   ├── 0002_runtime_cleanup.sql
│   │   │   └── 0003_compat_resources.sql
│   │   ├── source_repo.go          # sources 表 CRUD
│   │   ├── collection_repo.go      # collections 表 CRUD
│   │   ├── template_repo.go        # templates 表 CRUD
│   │   ├── settings_repo.go        # app_settings 表 CRUD
│   │   ├── grant_repo.go           # download_grants 表 CRUD
│   │   ├── recycle_repo.go         # recycle_bin 表 CRUD
│   │   └── cache_repo.go           # source_cache 表 CRUD
│   ├── handler/
│   │   ├── env.go                  # GET /api/env
│   │   ├── source.go               # /api/sources CRUD
│   │   ├── collection.go           # /api/collections CRUD
│   │   ├── template.go             # /api/templates CRUD
│   │   ├── settings.go             # /api/settings
│   │   ├── storage.go              # /api/storage 导出导入
│   │   ├── share.go                # /api/shares CRUD
│   │   ├── recycle.go              # /api/recycle-bin
│   │   ├── preview.go              # /api/preview/*
│   │   ├── link.go                 # /api/link/*
│   │   ├── flow.go                 # /api/source/flow/*
│   │   ├── tools.go                # /api/proxy/parse, /api/rule/parse, /api/utils/node-info
│   │   ├── script.go               # /api/scripts (注册表元数据)
│   │   └── download.go             # /download/* 公开下载
│   ├── middleware/
│   │   ├── auth.go                 # admin token 鉴权
│   │   ├── cors.go                 # CORS
│   │   ├── security.go             # 安全响应头
│   │   ├── bodylimit.go            # 请求体大小限制
│   │   └── downloadhost.go         # 下载域名隔离
│   ├── service/
│   │   ├── subscription.go         # 核心订阅处理管线
│   │   ├── fetcher.go              # 远程源抓取 + 缓存查询
│   │   └── concurrency.go          # 并发控制 worker pool
│   ├── proxy/
│   │   ├── parser.go               # 统一入口:parseProxies
│   │   ├── format.go               # 格式检测 + Base64 解码
│   │   ├── uri_parser.go           # 13 种 URI 协议解析
│   │   ├── client_parser.go        # QX/Surge/Loon 配置行解析
│   │   └── normalize.go            # 节点归一化 + 预览 ID
│   ├── filter/
│   │   ├── pipeline.go             # applyFilters 管线执行器
│   │   ├── include_exclude.go      # include/exclude
│   │   ├── rename.go               # rename
│   │   ├── delete_field.go         # delete-field
│   │   ├── dedupe.go               # dedupe (delete + rename)
│   │   ├── sort.go                 # sort + regex-sort
│   │   ├── flag.go                 # flag operator
│   │   ├── quick.go                # quick settings
│   │   ├── resolve.go              # DNS resolve
│   │   ├── custom.go              # 声明式自定义规则链 (替代 JS 脚本)
│   │   └── util.go                 # getByPath/setByPath/compileRegex
│   ├── render/
│   │   ├── mihomo.go               # mihomo/stash YAML
│   │   ├── surge.go                # surge + surge-mac
│   │   ├── surfboard.go            # surfboard
│   │   ├── loon.go                 # loon
│   │   ├── qx.go                   # quantumult x
│   │   ├── egern.go                # egern YAML
│   │   ├── singbox.go              # sing-box JSON
│   │   ├── uri.go                  # v2ray/uri/shadowrocket
│   │   └── json.go                 # JSON 原始输出
│   ├── rules/
│   │   └── converter.go            # 分流规则转换
│   ├── template/
│   │   ├── builtin.go              # 6 套内置模板定义
│   │   └── render.go               # proxy-group 展开 + 模板渲染
│   └── util/
│       ├── token.go                # token 生成 + hash + 验证
│       ├── path.go                 # 点号路径 get/set
│       ├── base64.go               # base64 编解码
│       ├── ip.go                   # IPv4/IPv6 检测
│       └── flag.go                 # 国旗检测 + 移除
├── config/
│   └── config.example.yaml         # 配置文件示例
├── web/
│   └── dist/                       # 前端构建产物 (embed.FS)
└── go.mod

3. 配置设计

3.1 配置文件 (config.yaml)

# 服务配置
server:
  host: "0.0.0.0"
  port: 3000
  read_timeout: 30s
  write_timeout: 60s
  body_limit: 4194304  # 4 MiB

# 数据库
database:
  path: "./data/sub-store.db"

# 鉴权
auth:
  admin_token: ""           # 必填,启动时检查
  download_token: ""        # 必填,启动时检查
  download_hosts: []        # 纯下载域名列表

# 远程源抓取
fetcher:
  default_timeout: 30s
  default_user_agent: "clash.meta/v1.19.24"
  default_flow_user_agent: "clash.meta/v1.19.24"
  concurrency: 3
  concurrency_wait: 0s
  cache_ttl: 300s
  cache_stale_on_error: true
  max_source_urls: 8
  max_response_bytes: 2097152     # 2 MiB
  max_total_bytes: 12582912       # 12 MiB

# 回收站
recycle:
  max_entries: 50

# 应用
app:
  name: "Sub-Store"
  version: "1.0.0"

3.2 Viper 加载策略

  1. 默认值 (config.SetDefault)
  2. 配置文件 (config.yaml,路径由 --config flag 指定,默认 ./config.yaml)
  3. 环境变量(前缀 SUB_STORE_._,如 SUB_STORE_AUTH_ADMIN_TOKEN
  4. 命令行 flagcobra

4. 数据库 Schema

直接复用原项目的 3 个 migration SQL,适配 modernc.org/sqlite 语法(基本兼容)。

4.1 表结构

-- 0001_initial.sql
CREATE TABLE IF NOT EXISTS sources (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  type TEXT NOT NULL DEFAULT 'remote',
  url TEXT NOT NULL DEFAULT '',
  content TEXT NOT NULL DEFAULT '',
  enabled INTEGER NOT NULL DEFAULT 1,
  filters_json TEXT NOT NULL DEFAULT '[]',
  meta_json TEXT NOT NULL DEFAULT '{}',
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS collections (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  source_ids_json TEXT NOT NULL DEFAULT '[]',
  filters_json TEXT NOT NULL DEFAULT '[]',
  template_id TEXT NOT NULL DEFAULT 'acl4ssr-mihomo',
  ignore_failed INTEGER NOT NULL DEFAULT 1,
  enabled INTEGER NOT NULL DEFAULT 1,
  meta_json TEXT NOT NULL DEFAULT '{}',
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS templates (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  target TEXT NOT NULL DEFAULT 'mihomo',
  config_json TEXT NOT NULL,
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS app_settings (
  id TEXT PRIMARY KEY,
  value_json TEXT NOT NULL DEFAULT '{}',
  updated_at INTEGER NOT NULL
);

-- 0003_compatibility_resources.sql
CREATE TABLE IF NOT EXISTS download_grants (
  id TEXT PRIMARY KEY,
  token_hash TEXT NOT NULL UNIQUE,
  resource_type TEXT NOT NULL CHECK (resource_type IN ('source', 'collection')),
  resource_id TEXT NOT NULL,
  target TEXT NOT NULL DEFAULT '',
  expires_at INTEGER,
  enabled INTEGER NOT NULL DEFAULT 1,
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_download_grants_token_hash
ON download_grants(token_hash);

CREATE INDEX IF NOT EXISTS idx_download_grants_resource
ON download_grants(resource_type, resource_id);

CREATE TABLE IF NOT EXISTS recycle_bin (
  id TEXT PRIMARY KEY,
  resource_type TEXT NOT NULL CHECK (resource_type IN ('source', 'collection', 'template', 'share')),
  resource_id TEXT NOT NULL,
  snapshot_json TEXT NOT NULL,
  deleted_at INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_recycle_bin_deleted_at
ON recycle_bin(deleted_at DESC);

-- 0004_source_cache.sql
CREATE TABLE IF NOT EXISTS source_cache (
  cache_key  TEXT PRIMARY KEY,           -- sha256(url + "\n" + userAgent)
  content    TEXT NOT NULL,              -- 远程源响应体文本
  metadata   TEXT NOT NULL DEFAULT '{}', -- JSON: subscription-userinfo / etag / last-modified / ...
  cached_at  INTEGER NOT NULL,           -- 写入时间戳 (unix seconds)
  ttl        INTEGER NOT NULL DEFAULT 300 -- TTL 秒数
);

CREATE INDEX IF NOT EXISTS idx_source_cache_expiry
ON source_cache(cached_at + ttl);

注:原项目 0002 migration 是清理旧内置模板行,Go 版无需复制此历史包袱,初始 schema 直接不含内置模板行。 source_cache 表替代原项目的 Workers Cache API,重启后缓存仍有效。


5. 功能模块开发计划

Phase 0:项目骨架 (P0)

目标:可编译、可启动、可连接数据库

任务 产出 依赖库
go mod init + 依赖引入 go.mod 全部
cobra root/serve/version 命令 cmd/*.go cobra
viper 配置加载 internal/config/config.go viper
logrus 日志初始化 日志格式化、级别、输出 logrus
SQLite 连接初始化 internal/database/db.go modernc/sqlite, sqlx
goose 迁移执行(启动时自动) internal/database/migrations.go goose
3 个 migration SQL 文件 internal/database/migrations/
Fiber app 骨架 路由注册、健康检查 fiber/v3
优雅关闭 signal handling

验证sub-store serve 启动,GET /health 返回 200,数据库表创建成功。


Phase 1:数据层 (P0)

目标6 张表的完整 CRUDRepository 模式

任务 产出 原项目对照
类型定义 internal/model/*.go types.ts 全部类型
Target 别名归一化 model/target.go targets.ts
SourceRepository source_repo.go store.ts listSources/getSource/upsertSource/deleteSource/sortSources
CollectionRepository collection_repo.go store.ts listCollections/...
TemplateRepository template_repo.go store.ts listTemplates/... + 内置模板合并
SettingsRepository settings_repo.go store.ts getSettings/updateSettings (深合并)
GrantRepository grant_repo.go compatibility-resources.ts
RecycleRepository recycle_repo.go compatibility-resources.ts
内置模板定义 internal/template/builtin.go defaults.ts 6 套模板
ID 归一化 toId() util/ store.ts toId()
深合并 mergeDeep() util/ store.ts mergeDeep()
排序逻辑 (created_at = now+index) Repository 层 store.ts sortSources/sortCollections

关键设计

  • 内置模板在 Go 代码中定义为 var BuiltinTemplatesListTemplates 合并内置 + DB 查询
  • upsert 使用 INSERT ... ON CONFLICT(id) DO UPDATE SET ...
  • sortSources 使用事务批量 UPDATE SET created_at = ? WHERE id = ?
  • settings 深合并:递归 merge map[string]interface{}

验证:单元测试覆盖所有 Repository 的 CRUD + 排序 + 深合并。


Phase 2:中间件 + 基础 API (P0)

目标:鉴权、CORS、安全头、基础 API 端点

任务 产出 原项目对照
Admin 鉴权中间件 middleware/auth.go http.ts requireAdmin + isTokenValid
Token 验证 (SHA-256 + ConstantTimeCompare) util/token.go http.ts isTokenValid
Bearer token 提取 middleware/auth.go http.ts getBearerToken
CORS 中间件 middleware/cors.go http.ts applyCorsHeaders
安全头中间件 middleware/security.go http.ts applySecurityHeaders
Body limit 中间件 middleware/bodylimit.go bodyLimit()
下载域名隔离 middleware/downloadhost.go index.ts hostname 检查
统一响应格式 model/response.go http.ts success/failed
GET /api/env handler/env.go api.ts envPayload
GET /api/settings + PATCH /api/settings handler/settings.go api.ts settings
GET/POST /api/storage handler/storage.go api.ts exportStorage/importStorage
静态文件服务 embed.FS + Fiber static index.ts ASSETS fallback
全局错误处理 Fiber error handler index.ts app.onError
SPA fallback 未匹配路由返回 index.html notFound handler

验证curl -H "Authorization: Bearer <token>" /api/env 返回环境信息。


Phase 3:源/集合/模板 API (P0)

目标:完整的管理 API,不含订阅处理

任务 产出 原项目对照
Source CRUD handler handler/source.go api.ts /api/sources 全部端点
Collection CRUD handler handler/collection.go api.ts /api/collections
Template CRUD handler handler/template.go api.ts /api/templates
排序 API handler/source.go api.ts PUT /api/sources, POST /api/sort/sources
ID 验证 ^[a-z0-9_-]{1,64}$ handler 层 api.ts validateRecordId
Source 验证 (remote URL / local content) handler 层 api.ts validateSource
Collection 验证 (sourceIds 引用检查) handler 层 api.ts validateCollection
内置模板保护 (不可删改) handler 层 api.ts BUILTIN_TEMPLATE_IDS
删除时归档到回收站 handler 层 api.ts archiveAndDeleteResource
引用检查 (source 被 collection 引用时不可删) handler 层 api.ts deleteSource
Template config 解析 (JSON/YAML → 归一化) handler 层 api.ts parseTemplateConfig
Template alias 归一化 (mixed-port→mixedPort) template/ store.ts normalizeMihomoTemplateConfig

验证:完整 API 测试 — 创建源/集合/模板,查询、更新、排序、删除(含回收站归档)。


Phase 4:代理协议解析器 (P0) 核心模块

目标:解析 13 种 URI 协议 + 2 种客户端配置行格式

任务 产出 原项目对照
ProxyNode 类型 model/types.go types.ts ProxyNode
格式检测 (JSON/YAML/URI 行) proxy/format.go subscription.ts looksLikeStructuredSubscription
Base64 解码 (maybe) proxy/format.go subscription.ts decodeMaybeBase64
统一入口 parseProxies proxy/parser.go subscription.ts parseProxies
JSON 代理数组解析 proxy/parser.go subscription.ts parseJsonProxies
YAML 代理数组解析 proxy/parser.go subscription.ts parseYamlProxies
URI 行解析入口 proxy/uri_parser.go subscription.ts parseProxyLines + parseProxyUri
vless:// 解析 proxy/uri_parser.go subscription.ts parseVless
vmess:// 解析 (base64 JSON) proxy/uri_parser.go subscription.ts parseVmess
trojan:// 解析 proxy/uri_parser.go subscription.ts parseTrojan
ss:// 解析 proxy/uri_parser.go subscription.ts parseShadowsocks
ssr:// 解析 (base64) proxy/uri_parser.go subscription.ts parseShadowsocksR
hysteria:// / hy:// 解析 proxy/uri_parser.go subscription.ts parseHysteria
hysteria2:// / hy2:// 解析 proxy/uri_parser.go subscription.ts parseHysteria2
tuic:// 解析 proxy/uri_parser.go subscription.ts parseTuic
anytls:// 解析 proxy/uri_parser.go subscription.ts parseAnytls
socks:// / socks5:// / socks5+tls:// proxy/uri_parser.go subscription.ts parseSocks
http:// / https:// (HTTP proxy) proxy/uri_parser.go subscription.ts parseHttpProxy
wireguard:// / wg:// proxy/uri_parser.go subscription.ts parseWireGuard
QX 配置行解析 proxy/client_parser.go subscription.ts parseQxProxyLine
Surge/Loon 配置行解析 proxy/client_parser.go subscription.ts parseNamedClientProxyLine
客户端选项解析 (CSV + key=value) proxy/client_parser.go subscription.ts splitClientCsv/parseClientOptions
节点归一化 + stripUndefined proxy/normalize.go subscription.ts normalizeProxy/stripUndefined
预览 ID 生成 proxy/normalize.go subscription.ts addPreviewIds/stableProxyId

正则兼容性注意:原项目用 JS RegExpGo 用 RE2。RE2 不支持反向引用和 lookahead/lookbehind。需要检查以下正则:

  • looksLikeStructuredSubscription 中的正则 — 应兼容
  • detectFlag 中的地区匹配正则 — 应兼容
  • compileRegex(?i) 前缀 — Go 用 (?i) 内联标志,兼容
  • filter 中的用户自定义正则 — 大部分兼容,极少数 lookahead 场景需文档说明限制

验证:为每种协议准备测试用例(URI → ProxyNode 结构比对),覆盖正常/异常/边界。


Phase 5:过滤器管线 (P0) 核心模块

目标11 种 FilterRule 类型的完整执行

任务 产出 原项目对照
管线执行器 applyFilters filter/pipeline.go subscription.ts applyFilters
include/exclude filter/include_exclude.go subscription.ts matchFilter
rename filter/rename.go subscription.ts renameProxies
delete-field filter/delete_field.go subscription.ts deleteFieldMatches
dedupe (delete + rename 两种模式) filter/dedupe.go subscription.ts handleDuplicateProxies
sort (asc/desc/random) filter/sort.go subscription.ts sortProxies
regex-sort filter/sort.go subscription.ts regexSortProxies
flag (add/remove + 台湾旗) filter/flag.go subscription.ts flagProxies
quick (udp/tfo/scert/useless/vmess-aead) filter/quick.go subscription.ts applyQuickSettings
resolve (DNS DoH 解析) filter/resolve.go subscription.ts resolveProxyDomains
custom (声明式规则链,替代 JS 脚本) filter/custom.go subscription.ts applyScriptAction
点号路径 get/set util/path.go subscription.ts getByPath/setByPath
正则编译 (支持 (?i) 前缀) util/ subscription.ts compileRegex
国旗检测/移除 util/flag.go subscription.ts detectFlag/removeFlag
ensureUniqueProxyNames filter/pipeline.go subscription.ts ensureUniqueProxyNames
IP 地址检测 (IPv4/IPv6) util/ip.go subscription.ts isIpv4/isIpv6
isUsefulProxy filter/quick.go subscription.ts isUsefulProxy
crypto/rand 安全随机 (shuffle) filter/sort.go subscription.ts secureRandomInt

resolve 过滤器细节

  • 5 个 DoH provider: Cloudflare / Google / Ali / Tencent / Custom
  • DoH JSON API: ?name=<host>&type=A|AAAA
  • 并发解析 + 过滤模式 (disabled/removeFailed/IPOnly/IPv4Only/IPv6Only)
  • 保留原域名为 servername/sni

验证:每种 filter 类型独立单元测试 + 管线顺序执行测试。


Phase 6:目标渲染器 (P0) 核心模块

目标13 种客户端格式的完整渲染

任务 产出 原项目对照
渲染入口 renderTarget render/ 分发 subscription.ts renderTarget/renderBuildTarget
mihomo YAML (proxy-groups 展开 + 模板) render/mihomo.go subscription.ts renderMihomoYaml
proxy-group $all 展开 + filter 正则 render/mihomo.go subscription.ts expandGroupProxies
surge 文本行 render/surge.go subscription.ts toSurgeProxyLine
surge-mac (ssh/h2-connect/snell) render/surge.go subscription.ts toSurgeMacProxyLine
surfboard (ss/vmess/trojan/http/socks5) render/surfboard.go subscription.ts toSurfboardProxyLine
loon 文本行 render/loon.go subscription.ts toLoonProxyLine
quantumult x 文本行 render/qx.go subscription.ts toQxProxyLine
egern YAML render/egern.go subscription.ts toEgernProxy
sing-box JSON (含 PROXY/AUTO/DIRECT/REJECT) render/singbox.go subscription.ts renderSingBoxJson
URI 行 (13 种协议) render/uri.go subscription.ts toProxyUri
v2ray (base64 编码 URI 行) render/uri.go subscription.ts base64Utf8
JSON 原始 render/json.go subscription.ts JSON.stringify
目标兼容性过滤 isTargetCompatible render/ subscription.ts isTargetCompatible
Content-Type 分配 render/ subscription.ts getTargetContentType
文本代理行工具 (joinTextProxy/quoteTextValue/sanitizeName) render/ subscription.ts joinTextProxy 等

验证:每种目标格式用固定 ProxyNode 集合渲染,比对输出。sing-box 输出需通过 JSON schema 验证。


Phase 7:订阅处理管线 (P0) 核心模块

目标:从源/集合到最终输出的完整管线

任务 产出 原项目对照
buildSubscriptionResult 入口 service/subscription.go subscription.ts buildSubscriptionResult
getSources (单源/集合模式) service/subscription.go subscription.ts getSources
loadProxyNodes (并发 + ignoreFailed) service/subscription.go subscription.ts loadProxyNodes
本地源加载 service/subscription.go subscription.ts loadSubscriptionRaw (local 分支)
远程源 URL 分割 (多行多 URL) service/fetcher.go subscription.ts splitSourceUrls
远程源抓取 (HTTP + 缓存 + 304 + stale) service/fetcher.go subscription.ts fetchSubscriptionUrl
UA 选择逻辑 service/fetcher.go subscription.ts getSourceUserAgent
并发控制 worker pool service/concurrency.go subscription.ts runWithConcurrency/runSettledWithConcurrency
缓存层 (SQLite source_cache 表 + SHA-256 key) database/cache_repo.go subscription.ts remoteCacheKey/safeCacheMatch/safeCachePut
缓存元数据存储 (subscription-userinfo 等) database/cache_repo.go subscription.ts setInternalMetadataHeader
响应元数据选择 service/subscription.go subscription.ts selectResponseMetadata
元数据从 source/Response 提取 service/subscription.go subscription.ts metadataFromSource/metadataFromResponse
流式响应读取 (限字节) service/fetcher.go read.ts readResponseText
预览 (previewSource/previewCollection) service/subscription.go subscription.ts previewSubscription/previewSourceContent
一次性转换 (convertSubscriptionContent) service/subscription.go subscription.ts convertSubscriptionContent
下载链接构建 handler/link.go api.ts buildDownloadLink
流量信息查询 (fetchFlowHeaders + parse) handler/flow.go api.ts fetchFlowHeaders/parseFlowHeaders

缓存设计(替代 Workers Cache API):

  • 使用 SQLite source_cache 表持久化缓存(无需额外依赖,重启不丢)
  • key: sha256(url + "\n" + userAgent) → 存储内容 + 元数据 JSON
  • TTL: 配置项 cache_ttl,默认 300s
  • 304 Not Modified: HTTP 请求携带 If-None-Match / If-Modified-Since
  • stale on error: 配置项 cache_stale_on_error
  • 异步写缓存: go func() (替代 waitUntil)
  • 定期清理: DELETE FROM source_cache WHERE cached_at + ttl < strftime('%s','now')

并发设计

// runWithConcurrency: 固定 worker 池
func RunWithConcurrency[T any](tasks []func() (T, error), concurrency int, wait time.Duration) ([]T, error)

// runSettledWithConcurrency: allSettled 语义 (失败跳过)
func RunSettledWithConcurrency[T any](tasks []func() (T, error), concurrency int, wait time.Duration) ([]Result[T])

验证:端到端测试 — 创建 remote 源 + local 源 + 集合,下载 mihomo/sing-box/uri 格式,验证节点数量和内容正确。


Phase 8:下载路由 + 分享/回收站 API (P1)

目标:公开下载端点 + 完整的分享/回收站功能

任务 产出 原项目对照
GET /collections/:name/:token handler/download.go download.ts
GET /sources/:name/:token handler/download.go download.ts
Download token 验证 (全局 + scoped grant) handler/download.go download.ts rejectInvalidDownloadToken
Target 推断 (显式 + UA 推断) handler/download.go download.ts getDownloadTarget
临时源覆盖 (url/content/ua query) handler/download.go download.ts getTemporarySourceOverride
响应头设置 (content-type, userinfo, etc.) handler/download.go download.ts renderDownload
Share CRUD handler handler/share.go api.ts /api/shares
Grant token 生成 (24 byte random → base64url) util/token.go compatibility-resources.ts randomToken
Grant token hash (SHA-256 hex) util/token.go compatibility-resources.ts sha256Hex
Scoped download 授权检查 handler/download.go compatibility-resources.ts authorizeScopedDownload
Recycle bin CRUD handler handler/recycle.go api.ts /api/recycle-bin
回收站恢复逻辑 (source/collection/template/share) handler/recycle.go api.ts restore
回收站自动裁剪 (50 条上限) recycle_repo.go compatibility-resources.ts archiveAndDeleteResource

验证:创建分享令牌 → 用令牌下载 → 验证 scoped 限制(资源/目标/过期)。删除源 → 回收站恢复。


Phase 9:工具 API (P1)

目标:格式转换器、规则转换、节点信息

任务 产出 原项目对照
POST /api/proxy/parse handler/tools.go api.ts proxy/parse
POST /api/rule/parse handler/tools.go api.ts rule/parse
规则转换器 rules/converter.go rules.ts convertRules
规则类型别名归一化 rules/converter.go rules.ts KIND_ALIASES
QX 规则特殊处理 rules/converter.go rules.ts qxKind
POST /api/utils/node-info handler/tools.go api.ts utils/node-info
GET /api/scripts handler/script.go api.ts scripts

规则转换细节

  • 支持规则类型: DOMAIN, DOMAIN-SUFFIX, DOMAIN-KEYWORD, IP-CIDR, IP-CIDR6, GEOIP, GEOSITE, PROCESS-NAME, DST-PORT, MATCH
  • 别名映射: HOST→DOMAIN, FINAL→MATCH, DEST-PORT→DST-PORT 等
  • QX 特殊: DOMAIN→HOST, IP-CIDR6→IP6-CIDR, MATCH→FINAL
  • 输入格式: YAML (rules: 列表) 或纯文本行

验证:各目标格式规则转换测试 + 节点信息查询测试。


Phase 10:声明式自定义规则 (P2) — 合并入 Phase 5

原计划的 goja JS 脚本引擎已移除。原项目的脚本能力(filter/operator 自定义 JS)改为声明式规则链实现,归入 Phase 5 过滤器管线的 custom 类型。

目标:用声明式规则组合替代 JS 脚本,覆盖原脚本的所有实际使用场景

设计custom filter 类型,内部为有序规则链,每条规则是一个原子操作:

{
  "type": "custom",
  "args": {
    "rules": [
      { "action": "set", "field": "name", "mode": "regex-replace", "pattern": "【.*?】", "replacement": "" },
      { "action": "include", "field": "name", "pattern": "香港|日本" },
      { "action": "set", "field": "udp", "value": true },
      { "action": "delete", "field": "tls.skipCertVerify" },
      { "action": "rename", "template": "{country}-{server}" }
    ]
  }
}
任务 产出 原项目对照
custom filter 类型实现 filter/custom.go scripts.ts applyScriptAction
原子操作:set/delete/include/exclude filter/custom.go 脚本中对节点字段的操作
原子操作:regex-replace (字段值正则替换) filter/custom.go 脚本中 p.name.replace(/.../, ...)
原子操作:rename (模板化重命名) filter/custom.go 脚本中节点名拼接逻辑
原子操作:条件分支 (when 字段匹配) filter/custom.go 脚本中 if/else 逻辑
参数验证 filter/custom.go scripts.ts validateScriptActions
$arguments 支持 custom args 直接映射 scripts.ts $arguments
ProxyUtils 等价能力 复用 util/flag.go, util/ip.go, util/base64.go scripts.ts PROXY_UTILS

与原项目差异

  • 原项目:用户编写 JS 函数,运行时闭包/eval 执行
  • Go 版:用户配置声明式规则链,Go 原生执行
  • 优势:无任意代码执行风险、无需 JS 引擎、前端可构建可视化规则编辑器、性能更高
  • 限制:不支持 JS 的完整编程能力(循环、递归、复杂条件组合),但实际场景中 99% 的脚本都是字段操作 + 正则 + 条件过滤,声明式规则完全覆盖

验证:将原项目常见脚本用例转换为声明式规则,验证等效输出。


Phase 11:集成与打磨 (P1)

任务 说明
前端 embed.FS 集成 //go:embed web/distFiber static + SPA fallback
请求日志中间件 logrus 结构化日志
Panic recovery 中间件 Fiber Recover
速率限制 (可选) Fiber limiter 中间件
配置热更新 (可选) viper WatchConfig
健康检查端点 GET /health
pprof 端点 (可选) debug 模式
集成测试 端到端 API 测试
Dockerfile 多阶段构建
systemd service 文件 可选,用户提供时生成

6. API 端点完整清单

管理 API (需 Admin Token)

方法 路径 Phase 说明
GET /api/env 2 环境信息
GET /api/scripts 9 脚本注册表 (仅元数据,供前端展示可选脚本)
GET /api/settings 2 应用设置
PATCH /api/settings 2 更新设置
GET /api/storage 2 导出备份
POST /api/storage 2 导入备份
GET /api/sources 3 源列表
POST /api/sources 3 创建源
PUT /api/sources 3 排序源
POST /api/sort/sources 3 排序源 (兼容)
GET /api/sources/:name 3 查询单源
PATCH /api/sources/:name 3 更新源
DELETE /api/sources/:name 3 删除源
GET /api/collections 3 集合列表
POST /api/collections 3 创建集合
PUT /api/collections 3 排序集合
POST /api/sort/collections 3 排序集合 (兼容)
GET /api/collections/:name 3 查询单集合
PATCH /api/collections/:name 3 更新集合
DELETE /api/collections/:name 3 删除集合
GET /api/templates 3 模板列表
POST /api/templates 3 创建模板
GET /api/templates/:name 3 查询模板
PATCH /api/templates/:name 3 更新模板
DELETE /api/templates/:name 3 删除模板
GET /api/shares 8 分享列表
POST /api/shares 8 创建分享
PATCH /api/shares/:id 8 更新分享
DELETE /api/shares/:id 8 删除分享
GET /api/recycle-bin 8 回收站列表
DELETE /api/recycle-bin/:id 8 彻底删除
POST /api/recycle-bin/:id/restore 8 恢复
POST /api/preview/source 7 源预览
POST /api/preview/collection 7 集合预览
GET /api/link/source/:name 7 源下载链接
GET /api/link/collection/:name 7 集合下载链接
GET /api/source/flow/:name 7 流量信息
POST /api/proxy/parse 9 代理转换
POST /api/rule/parse 9 规则转换
POST /api/utils/node-info 9 节点信息

公开下载 API (需 Download Token 或 Scoped Grant)

方法 路径 Phase 说明
GET /collections/:name/:token 8 下载集合
GET /sources/:name/:token 8 下载源

其他

方法 路径 Phase 说明
GET /health 0 健康检查
GET /* 11 静态文件 (SPA fallback)

7. 开发顺序与依赖关系

Phase 0: 项目骨架
    │
    ├──→ Phase 1: 数据层 (model + repository)
    │        │
    │        └──→ Phase 2: 中间件 + 基础 API
    │                 │
    │                 └──→ Phase 3: 源/集合/模板 API
    │                          │
    │                          ├──→ Phase 4: 代理协议解析器 ─────┐
    │                          │                                │
    │                          ├──→ Phase 5: 过滤器管线 ────────┤
    │                          │                                │
    │                          └──→ Phase 6: 目标渲染器 ────────┤
    │                                                           │
    │                                                           ↓
    │                    Phase 7: 订阅处理管线 (依赖 4+5+6)
    │                           │
    │                           ├──→ Phase 8: 下载 + 分享/回收站
    │                           │
    │                           └──→ Phase 9: 工具 API
    │                                    │
    └────────────────────────────────────┴──→ Phase 10: 声明式规则 (Phase 5 扩展)
                                             │
                                             └──→ Phase 11: 集成打磨

关键路径: Phase 0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 11

可并行:

  • Phase 4/5/6 可并行开发(三者独立,7 依赖三者完成)
  • Phase 9 可与 Phase 8 并行

8. 测试策略

8.1 测试分层

层级 范围 工具
单元测试 每个函数/方法 testing + testify/assert
Repository 测试 临时 SQLite 文件 testing + sqlx
API 集成测试 HTTP 端到端 httptest + Fiber test utils
协议解析测试 每种 URI/客户端格式 表驱动测试
渲染测试 每种目标格式 固定输入 + golden file 比对
端到端测试 完整订阅流程 测试 HTTP server + 真实 HTTP 请求

8.2 关键测试用例

协议解析 (Phase 4):

vless://uuid@host:443?security=reality&pbk=xxx&fp=chrome&type=ws#name
→ ProxyNode{type:"vless", server:"host", port:443, uuid:"uuid", ...}

过滤器 (Phase 5):

input: [节点A(香港), 节点B(日本), 节点C(美国)]
filter: {type:"include", field:"name", pattern:"香港|日本"}
output: [节点A, 节点B]

渲染 (Phase 6):

input: [ProxyNode{type:"ss", name:"test", server:"1.2.3.4", port:8388, cipher:"aes-256-gcm", password:"pass"}]
target: mihomo
output: YAML with proxies: [{name:"test", type:"ss", ...}]

端到端 (Phase 7):

1. 创建 remote source (url: mock server)
2. 创建 collection (sourceIds: [source.id])
3. GET /collections/:id/xxx?target=mihomo
4. 验证 YAML 输出包含正确的 proxy 节点

8.3 测试数据

  • 13 种协议各准备 2-3 个 URI 测试样本
  • 每种 filter 类型准备输入/输出 golden case
  • 每种渲染目标准备 golden output file
  • Mock 远程订阅服务器返回固定内容

9. 部署方案

9.1 单二进制

# 构建
go build -o sub-store ./main.go

# 运行(启动时自动执行迁移,幂等)
./sub-store serve --config config.yaml

9.2 配置

  • --config flag 指定配置文件路径
  • 环境变量 SUB_STORE_* 覆盖配置
  • admin_token / download_token 必填,启动时检查

9.3 Docker (可选,用户需求时)

# 多阶段构建
FROM golang:1.23 AS builder
COPY . /src
WORKDIR /src
RUN CGO_ENABLED=0 go build -o /sub-store ./main.go

FROM alpine:latest
COPY --from=builder /sub-store /sub-store
COPY config.yaml /config.yaml
EXPOSE 3000
ENTRYPOINT ["/sub-store", "serve", "--config", "/config.yaml"]

注:使用 modernc.org/sqlite (纯 Go)CGO_ENABLED=0 可静态编译。


10. 风险与注意事项

10.1 正则兼容性

Go 的 regexp 包使用 RE2 引擎,不支持:

  • 反向引用 (\1)
  • Lookahead ((?=...), (?!...))
  • Lookbehind ((?<=...), (?<!...))

原项目中用户自定义的 filter 正则可能使用这些特性。解决方案:

  • 文档说明限制
  • 对于 (?i) 前缀,Go 支持 (?i) 内联标志
  • 极端情况考虑引入 github.com/dlclark/regexp2 (完整 PCRE 支持) 作为回退

10.2 JSON5 解析

原项目用 json5 npm 包解析代理节点 JSON。Go 标准库 encoding/json 不支持 JSON5(单引号、尾逗号、注释等)。

解决方案:

  • 大多数订阅返回标准 JSON,标准库足够
  • 对于需要 JSON5 兼容的场景,使用 github.com/tidwall/gjson 进行宽容解析,或 github.com/yourbasic/json5

10.3 自定义规则安全性

声明式规则链不存在脚本注入风险(无任意代码执行)。需注意:

  • 正则 ReDoS:对用户输入的正则设置编译超时或回溯限制
  • 规则链深度:限制最大规则数量(默认 32 条)

10.4 并发安全

  • SQLite 写锁:modernc.org/sqlite 支持 WAL 模式,读写可并发
  • SetMaxOpenConns(1):避免多连接 database is lockedWAL 下读不阻塞写
  • PRAGMA busy_timeout=5000:写锁等待 5 秒
  • source_cache 表:WAL 模式下读写不互斥,缓存读写不影响其他表操作
  • 异步缓存写入:sync.WaitGroup + context 管理生命周期,graceful shutdown 时等待完成(5s 超时)
  • 缓存操作错误吞咽:recover() + 日志,不影响订阅生成
  • Repository 层:无状态,安全
  • 全局状态:仅配置和缓存,均为并发安全

10.5 ProxyNode 类型与深拷贝

ProxyNode 定义为 type ProxyNode map[string]any(详见 docs/review-resolutions.md #1)。

  • 深拷贝:json.Marshaljson.Unmarshal(简单可靠,性能可接受)
  • 类型安全访问:util/path.go 提供 GetByPath / SetByPath / GetString / GetInt / GetBool 等辅助函数
  • stripUndefined:遍历 map,删除值为 nil"" 的 key

11. 原项目代码量评估

模块 原项目行数 预估 Go 行数 复杂度
types.ts 188 ~250
store.ts 644 ~600
subscription.ts 2430 ~3000 极高
rules.ts 159 ~180
defaults.ts 246 ~300
targets.ts 59 ~80
scripts.ts 157 ~100 低 (custom 规则替代,仅保留注册表元数据)
compatibility-resources.ts 261 ~300
http.ts 77 ~100
limits.ts 6 ~10
read.ts 44 ~50
api.ts (routes) 896 ~1000
download.ts 165 ~200
index.ts 72 ~150
合计 5404 ~6320
+ config/cmd/middleware/util ~1500
总计 ~7800

12. 里程碑

里程碑 Phase 交付物 验证标准
M1: 可启动 0-1 二进制可运行,DB 初始化 serve 启动,表创建
M2: 基础 API 2-3 源/集合/模板 CRUD API 测试全通过
M3: 协议解析 4 13 种 URI 解析 解析测试全通过
M4: 过滤+渲染 5-6 11 种 filter + 13 种渲染 单元测试全通过
M5: 订阅管线 7 完整下载流程 端到端测试通过
M6: 完整后端 8-9 下载/分享/回收站/工具 全部 API 测试通过
M7: 声明式规则 10 custom filter 规则链 规则转换测试通过
M8: 生产就绪 11 前端集成/部署/Docker 集成测试通过