Skip to content

Latest commit

 

History

History
611 lines (446 loc) · 19.6 KB

File metadata and controls

611 lines (446 loc) · 19.6 KB

开发指南

欢迎参与 SublinkPro 的开发。本指南聚焦于:

  • 如何在本地跑通前后端开发环境
  • 生产构建链路实际是什么
  • 哪些文件/目录是高价值入口
  • 解锁检测相关扩展点在哪里

📁 项目结构

sublinkPro/
├── api/                     # HTTP API / controller
├── models/                  # 数据模型、持久化、迁移
├── services/                # 业务服务与后台子系统
│   ├── scheduler/           # 定时任务与任务调度
│   ├── mihomo/              # mihomo 集成(测速、DNS、Host、代理出站等)
│   └── unlock/              # 解锁检测注册表、运行时、checker
├── routers/                 # 路由注册
├── node/                    # 订阅与协议解析/转换逻辑
├── utils/                   # 通用工具函数
├── database/                # 数据库连接与方言支持
├── cache/                   # 缓存层
├── dto/                     # DTO / 表单结构
├── webs/                    # React + Vite 前端
│   └── src/
│       ├── api/            # 前端请求边界
│       ├── views/          # 页面级功能
│       ├── components/     # 公共组件
│       ├── utils/          # 前端通用工具
│       ├── themes/         # 主题与 MUI overrides
│       └── routes/         # 路由定义
├── template/                # 模板文件
├── docs/                    # 文档
├── static/                  # 生产构建时前端产物放置目录
├── main.go                  # 应用入口
├── Dockerfile               # Docker 构建
└── README.md

🔧 技术栈

层级 技术
后端框架 Go + Gin
ORM GORM
数据库 SQLite(默认)/ MySQL / PostgreSQL
前端框架 React 19 + Vite
UI Material UI
前端包管理 Yarn 4
调度 robfig/cron

💻 本地开发

1. 克隆项目

git clone https://github.com/ZeroDeng01/sublinkPro.git
cd sublinkPro

2. 后端开发

建议使用 Go 1.26.3 或更高版本,与仓库、Docker 和 CI 保持一致。

go mod download
go run main.go

默认后端监听 :8000

3. 前端开发

webs/ 下执行:

yarn install
yarn run start

Vite 默认开发端口为 3000,并通过 /api 代理后端请求。

4. 前端校验

webs/ 下执行:

yarn run lint
yarn run build
yarn run lint:fix
yarn run prettier

前端开发完成后必须至少运行 yarn run lint;涉及构建产物、资源路径、路由、base-path 或生产集成时,还必须运行 yarn run build

Note

当前仓库没有权威的前端 testtypecheck 脚本。不要在文档或自动化里发明不存在的校验流程。

5. 后端校验

后端开发完成后,Go 文件必须通过 gofmt 格式化,并运行 golangci-lint 静态检查与相关测试:

gofmt -w <changed-go-files>
golangci-lint run
go test ./...

新增或修改关键业务逻辑、接口契约、权限判断、配置语义、迁移、调度任务、mihomo 集成、协议解析或数据转换时,应同步补充或更新对应 Go 测试。GitHub 发布构建会在构建二进制前运行 golangci-lint 和全仓 go test ./...

6. PR 自动检查

.github/workflows/pr-checks.yml 会在 PR 打开、重新打开或标记 ready for review 时自动运行,用于快速判断 PR 是否满足基础质量要求。后续修复提交不会自动重复消耗 Actions;PR 作者本人或仓库管理员确认修复完成后,可在 PR 评论 /recheck 手动触发新一轮检查。

  • 后端:golangci-lintgo test ./...
  • 前端:yarn run lintyarn run build

每个检查 job 会写入 GitHub Step Summary,展示对应检查项是通过还是失败,方便作者和 reviewer 快速判断哪些工作已经完成、哪些还需要修复。

7. 普通后端构建

go build -o sublinkpro main.go

这适合开发环境或快速本地编译,不代表生产嵌入构建。

8. 生产构建(实际流程)

生产构建是两阶段;本地执行生产风格构建前,应先完成前端 lint / build 和后端格式、lint、测试校验:

# 1) 前端 lint 与构建
cd webs
yarn run lint
yarn run build

# 2) 后端格式、lint 与测试
cd ..
gofmt -w <changed-go-files>
golangci-lint run
go test ./...

# 3) 准备生产静态资源
rm -rf static && mkdir -p static
cp -R webs/dist/. static/

# 4) 构建生产后端(嵌入前端资源)
CGO_ENABLED=0 go build -tags=prod -ldflags="-s -w" -o sublinkPro

Important

如果你修改了前端资源路径、PWA 资产、base-path、嵌入逻辑或静态资源服务方式,必须同时验证:

  • webs 本地开发模式
  • 前端 build 产物
  • static/ 复制后的生产嵌入构建

🧭 关键运行时约定

路径边界

  • 前端 UI:/SUBLINK_WEB_BASE_PATH 指定的路径
  • API:始终在 /api/*
  • 订阅/分享:始终在 /c/*

SUBLINK_WEB_BASE_PATH 只影响 Web UI,不影响 API 和订阅获取路径。

运行时目录

这些目录属于运行时状态,请谨慎处理:

  • db/
  • logs/
  • template/
  • out/

其中:

  • db/:数据库、配置文件、GeoIP 等本地数据
  • template/:模板文件
  • logs/:运行日志

🔍 高价值入口文件

模块 文件 说明
节点测速 services/scheduler/speedtest_task.go 延迟、速度、质量、解锁检测主流程
解锁检测 services/unlock/*.go Provider registry / runtime / orchestrator / checkers
标签规则 services/tag_service.go 自动标签规则执行
订阅生成 api/clients.go 订阅输出与节点筛选、rename
链式代理 api/subscription_chain.go / models/subscription_chain_rule.go 订阅链式代理规则与条件选节点
Host 管理 models/host.go Host 映射、批量写入、缓存管理
DNS 解析 services/mihomo/dns_resolver.go 自定义 DNS 与代理解析
数据迁移 models/db_migrate.go 数据库迁移脚本

🔌 新增协议接入指南

当前协议系统已经重构为自注册 + 能力接口模式。目标是:

新增一种协议时,开发者只需要在 node/protocol/ 下增加一个协议文件,在这个文件里实现协议本身、导出能力,并完成注册。

协议扩展入口

建议直接参考:

  • node/protocol/protocol_demo.go:标准示例协议
  • 任意真实协议文件,如:
    • node/protocol/vmess.go
    • node/protocol/ss.go
    • node/protocol/http.go

当前协议体系结构

中心能力位于 node/protocol/protocol_meta.go

  • Protocol:核心协议规范
  • ProxyCapable:支持 Clash Proxy 结构体转换
  • SurgeCapable:支持 Surge 行导出
  • SupportsClient(...):声明协议对 Clash / mihomo / v2ray / Surge 等客户端的订阅输出兼容性
  • MustRegisterProtocol(...):协议注册入口

新增协议后,以下链路会自动接入,不需要再去额外补 switch:

  • 协议识别(alias / scheme)
  • 节点 raw 解析
  • 节点 raw 字段更新
  • 节点 identity 提取(名称 / host / port / address)
  • 去重字段读取
  • 节点链接重命名
  • LinkToProxy 分发
  • EncodeSurge 分发
  • EncodeProxyLink 分发
  • v2ray raw 输出兼容性过滤(通过协议文件内声明的客户端支持能力)
  • 协议 UI 元数据输出

新增协议的推荐步骤

  1. node/protocol/ 新增一个协议文件,例如:

    node/protocol/myprotocol.go
    
  2. 定义协议结构体。

    结构体字段会作为默认 UI 字段元数据来源,因此命名要稳定、清晰。

  3. 实现协议链接的 Decode / Encode

    至少要保证:

    • DecodeXxxURL(string) (Xxx, error)
    • EncodeXxxURL(Xxx) string
  4. 如果需要从 Clash Proxy 反推链接,补 ConvertProxyToXxx(proxy Proxy) Xxx

  5. 如果协议支持 Clash 导出,在同一文件中实现 buildXxxProxy(link Urls, config OutputConfig)

  6. 如果协议支持 Surge 导出,在同一文件中实现 buildXxxSurgeLine(link string, config OutputConfig)

  7. 在同一个文件里 init() 自注册。

  8. 在协议文件内声明客户端兼容性;默认规则如下:

    • newProtocolSpec(...) 默认支持 ClientV2ray
    • newProxyProtocolSpec(...) 默认支持 ClientClashClientMihomoClientV2ray
    • newProxySurgeProtocolSpec(...) 默认支持 ClientClashClientMihomoClientV2rayClientSurge
    • 如果协议只适合部分客户端,在 base 上调用 WithClientSupport(...) 覆盖默认值,例如 Mieru 只声明 ClientClashClientMihomo,因此 v2ray / Surge 输出会跳过它。

    可用客户端常量当前包括:ClientClashClientMihomoClientV2rayClientSurge。新增客户端渲染器前,不要只在 api/clients.go 写协议特例,应先让协议注册文件声明支持关系。

标准注册模板

func init() {
    base := newProtocolSpec(
        "myprotocol",
        []string{"myprotocol://"},
        "MyProtocol",
        "#1976d2",
        "M",
        MyProtocol{},
        "Name",
        DecodeMyProtocolURL,
        EncodeMyProtocolURL,
        func(p MyProtocol) LinkIdentity {
            return buildIdentity("myprotocol", p.Name, p.Server, utils.GetPortString(p.Port))
        },
        // 可选:手工字段 schema,若不传则从结构体反射生成
    )

    // 可选:覆盖客户端兼容性;不写则使用构造器默认值。
    // base = base.WithClientSupport(ClientClash, ClientMihomo)

    MustRegisterProtocol(newProxySurgeProtocolSpec(
        base,
        buildMyProtocolProxy,
        func(proxy Proxy) bool {
            return proxyTypeMatches(proxy, "myprotocol")
        },
        ConvertProxyToMyProtocol,
        EncodeMyProtocolURL,
        buildMyProtocolSurgeLine,
    ))
}

如果协议只支持 Clash,不支持 Surge,可以使用:

MustRegisterProtocol(newProxyProtocolSpec(...))

如果协议只是演示协议、只需要解析和 UI 元数据,也可以只注册 newProtocolSpec(...)

如果协议有不适合完整 Decode / Import 的额外分享链接前缀,但仍需要参与客户端兼容性判断,可以使用 WithClientSupportAliases(...) 增加“仅兼容性检测”的 alias。这样不会把该前缀注册成完整协议解析入口;例如 Mieru 对 mierus:// 只用于判断 v2ray 不应输出,并不会声明已支持官方 mierus:// 分享链接的逐字段解析。

VLESS XHTTP 映射约定

当前仓库对 vless + xhttp 的处理遵循以下约定:

  • URL 顶层字段:
    • type=xhttp → Clash / mihomo network: xhttp
    • pathxhttp-opts.path
    • hostxhttp-opts.host
    • modexhttp-opts.mode
    • extra → 先解码 JSON,再映射到 xhttp-opts
  • extra 当前已支持的字段:
    • headersxhttp-opts.headers
    • noGRPCHeaderxhttp-opts.no-grpc-header
    • xPaddingBytesxhttp-opts.x-padding-bytes
    • downloadSettingsxhttp-opts.download-settings
  • downloadSettings 中当前已支持的常见子字段包括:
    • pathhostheadersserverporttlsalpn
    • skipCertVerifyskip-cert-verify
    • clientFingerprintclient-fingerprint
    • privateKeyprivate-key
    • realityOptsreality-opts
    • echOptsech-opts

需要特别区分两类 ECH 语义:

  • VLESS URL 顶层 ech=... 对应的是 Xray/VLESS 的 echConfigList 语义,不是所有写法都能与 mihomo ech-opts 无损互转。
  • ech 是固定 base64 ECHConfig 时,会映射到顶层 ech-opts.enable: true + ech-opts.config
  • ech 是 Xray 的 DNS / URI 风格(如 domain+https://...)时,只会按 mihomo 可表达的能力做最佳努力映射:保留 enable: true,并在可识别时写入 query-server-name;其中 resolver URI 本身不会被保留。
  • 当节点来自 Clash/mihomo YAML 导入链路(包括机场订阅中的 Clash YAML 与手动 Clash YAML 导入)时,如果只有顶层 ech-opts.query-server-name 可恢复,系统会在写入 Node.Link 前按本地兼容规则重建为 ech=<query-server-name>+https://dns.alidns.com/dns-query
  • extra.downloadSettings.echOpts 只用于 xhttp 的嵌套下载设置映射,会转换成 mihomo xhttp-opts.download-settings.ech-opts
  • 顶层 echextra.downloadSettings.echOpts 都会映射到各自层级的 ech-opts,但两者不会互相合并或覆盖。

实现时需要注意:

  • xhttp 只允许出现在 VLESS 上,不要复用到其他协议。
  • 不要把 xhttp 静默降级成 httph2grpc
  • 用户在订阅设置中勾选“跳过证书验证”后,会通过 OutputConfig.Cert 强制覆盖输出配置;对于 xhttp,这条规则同时作用于顶层 skip-cert-verifydownload-settings.skip-cert-verify

字段元数据说明

newProtocolSpec(...) 最后可以追加 FieldMeta,用于驱动前端字段展示:

  • Name:字段名
  • Label:显示名称
  • Typestring / int / bool
  • Group:分组,如 basic / auth / transport / tls / advanced
  • Description:字段说明
  • Placeholder:输入占位提示
  • Options:枚举选项
  • Advanced:是否为高级字段
  • Secret:是否为敏感字段
  • Multiline:是否建议多行显示

如果不传 FieldMeta,系统会回退到结构体反射元数据,这样可以做到“最少接入”。

什么时候还需要改协议文件之外的地方?

理想目标是:只改协议文件并注册即可。

当前还保留少量“协议外工作”,但它们不属于协议核心接入:

  • 补该协议的单元测试
  • 如需对外说明,更新 README / docs 的支持矩阵
  • 如需更好的前端交互,再补字段元数据(仍可写在协议文件内)

正常情况下,你不应该再去改:

  • node/protocol/clash.go 的协议分发
  • node/protocol/surge.go 的协议分发
  • node/sub.go 的链接生成 switch
  • api/node.go 的协议判断
  • api/node_raw.go 的名称提取 switch

如果你发现新增协议还需要改这些地方,说明抽象出现了倒退,应优先修抽象而不是继续补 case。

ProtocolDemo 的用途

node/protocol/protocol_demo.go 不是生产协议,而是协议扩展模板。

它展示了:

  • 如何定义协议结构体
  • 如何实现 Decode / Encode
  • 如何补 LinkIdentity
  • 如何声明字段元数据
  • 如何实现 Clash / Surge 导出能力
  • 如何在一个文件里完成注册

新增真实协议时,建议直接复制 ProtocolDemo 的结构再改造成你的协议,而不是从零拼装。


⏰ 定时任务开发指南

SublinkPro 使用模块化定时任务系统,基于 robfig/cron

目录结构

services/scheduler/
├── manager.go
├── job_ids.go
├── subscription_task.go
├── speedtest_task.go
├── host_cleanup_task.go
├── reporter.go
├── utils.go
└── bridge.go

添加新任务的基本步骤

  1. job_ids.go 定义任务 ID
  2. services/scheduler/ 新增任务文件
  3. manager.go 的加载逻辑里接入
  4. 如有前端任务进度需求,接入 TaskManager

带进度报告的任务

func ExecuteYourTaskWithProgress() {
    tm := getTaskManager()

    task, ctx, err := tm.CreateTask(
        models.TaskTypeYourType,
        "你的任务名称",
        models.TaskTriggerScheduled,
        100,
    )
    if err != nil {
        utils.Error("创建任务失败: %v", err)
        return
    }

    taskID := task.ID

    for i := 1; i <= 100; i++ {
        select {
        case <-ctx.Done():
            utils.Info("任务被取消")
            return
        default:
        }

        tm.UpdateProgress(taskID, i, "当前处理项", map[string]interface{}{
            "status": "success",
        })
    }

    tm.CompleteTask(taskID, "任务完成", map[string]interface{}{
        "total": 100,
    })
}

🌍 解锁检测扩展指南

解锁检测沿用节点检测 / 测速策略链路,不额外起一套独立任务系统。

关键文件

  • api/node_check.go
  • models/node_check_profile.go
  • models/node.go
  • models/unlock.go
  • services/scheduler/speedtest_config.go
  • services/scheduler/speedtest_task.go
  • services/unlock/registry.go
  • services/unlock/runtime.go
  • services/unlock/orchestrator.go
  • services/unlock/checker_*.go

设计原则

  • 每个 Provider 一个独立 Checker
  • 统一 registry / orchestrator
  • 共享 runtime(代理 HTTP client、timeout、落地国家)
  • 统一结果结构:models.UnlockProviderResult

新增一个 Provider

  1. 新增 services/unlock/checker_<provider>.go
  2. 实现:
type UnlockChecker interface {
    Key() string
    Aliases() []string
    Check(runtime UnlockRuntime) models.UnlockProviderResult
}
  1. init() 中注册 RegisterUnlockChecker(...)
  2. 在 checker 内同时声明 Provider 元数据(展示名、分类、rename 变量等)
  3. 如新增了新的状态语义,在 services/unlock/meta.go 中补充状态元数据
  4. 更新 docs/features/unlock-check.md(仅在文档需要列举当前内置 Provider 时)

Important

当前前端的节点筛选、标签规则、链式代理条件、订阅编辑中的 unlock 选项都通过后端元数据动态消费。 正常情况下,新增一个 checker 不需要再去前端补 Provider / 状态枚举,也不需要手动同步多个页面的选项列表。

命名构建器变量

推荐使用 provider-specific 形式:

  • $Unlock(gemini)
  • $Unlock(openai)
  • $Unlock(netflix)

这些变量通过后端元数据动态下发。

多条件解锁筛选

当前节点列表与订阅过滤都支持多条规则。

  • 一条规则内部:AND
  • 多条规则之间:OR / AND 可选
  • 没有规则:表示不启用解锁筛选

Tag / Chain 规则中的解锁条件

当前 Tag 自动规则和 Chain 规则都已支持:

  • unlock_provider
  • unlock_status
  • unlock_keyword
  • unlock_result

推荐优先使用 unlock_providerunlock_status 做精确匹配;unlock_keyword 适合做模糊搜索。

这些字段的 schema、可用操作符、枚举值来源现在都由后端统一下发:

  • unlock_provider → 动态读取已注册 checker 的 Provider 列表
  • unlock_status → 动态读取后端状态元数据
  • unlock_keyword / unlock_result → 作为文本字段处理

解锁检测并行执行

当前单个节点内多个 Provider 检测由 services/unlock/orchestrator.go受控并行

  • 每个节点内部:多 Provider 并行
  • 使用小规模并发上限
  • 结果顺序保持稳定

🕐 Cron 表达式格式

本项目使用 5 字段 Cron 格式(不含秒):

字段 取值范围 说明
分钟 0-59 每小时的第几分钟
小时 0-23 每天的第几小时
1-31 每月的第几天
1-12 每年的第几月
0-6 每周的第几天(0=周日)

常用示例:

表达式 说明
*/5 * * * * 每 5 分钟
0 */2 * * * 每 2 小时
30 8 * * * 每天 8:30
0 0 * * 0 每周日 00:00
0 2 1 * * 每月 1 日 02:00

💡 开发建议

  1. 任务应尽量幂等
  2. 长任务支持取消 (ctx.Done())
  3. 修改配置语义时同步更新文档
  4. 前端命令、生产构建流程优先以 webs/package.json、CI、Dockerfile 为准
  5. 不要在文档中发明仓库里不存在的命令