mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-20 08:19:58 +00:00
Caddy 式进程内模块系统(详见 docs/plugin-architecture/MODULE-AUTHOR-GUIDE.md): - internal/plugin:命名空间注册表(init 注册/重复 panic)、Provision→Validate→Start→Stop 生命周期(Start 半途逆序回滚、Stop 逆序+ctx deadline)、Host 四项 ports 能力面、 modules: 配置子树(enabled 三态;viper 含点 key 手工提取)、Runtime 状态机 - internal/plugin/plugintest:模块测试夹具(NewHost Options + RunLifecycle) - internal/modules:standard/imports.go 唯一插装清单 + job.hello 示例模块(默认 disabled) - Wire/main 接入:Build+Start 失败 fail-fast(显式 Cleanup 防 leader 锁泄漏)、 runtime.Stop 在 cleanup 并行组首位(先于 Redis/Ent) - admin 可观测:GET /api/v1/admin/modules(只读,错误文本脱敏) - 默认配置零行为变更(Phase-0 安全网 + bench allocs 零漂移验证) 含两轮对抗式审计修复(1×P1 启动失败泄漏 leader 锁 + 11×P2)与回归测试。
134 lines
5.3 KiB
Go
134 lines
5.3 KiB
Go
// Package plugin 实现 Caddy 式进程内插件内核。
|
||
//
|
||
// 内核由四部分组成:
|
||
// - 模块注册表(registry.go):命名空间化的模块目录,模块包在 init() 期通过
|
||
// RegisterModule 自注册,编译期插装清单(internal/modules/standard)决定哪些模块被编译进来;
|
||
// - 配置子树(config.go):来自全局配置 `modules:` 的每模块配置(enabled + raw 私有配置);
|
||
// - 宿主能力面(host.go):模块可使用的全部宿主能力,只暴露 ports 级接口;
|
||
// - 生命周期驱动器(runtime.go):按注册表稳定序实例化 enabled 模块并驱动
|
||
// Provision → Validate → Start → Stop(逆序)生命周期。
|
||
//
|
||
// 注册表只是"目录",模块的实例化与生命周期统一由 Runtime 驱动。
|
||
package plugin
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
// ModuleID 是模块的全局唯一标识,采用点分层级命名,
|
||
// 形如 "job.hello"、"gateway.platform.anthropic"。
|
||
//
|
||
// 最后一段为模块名(Name),之前的部分为命名空间(Namespace)。
|
||
// 每一段只允许 [a-z0-9_-] 字符且不能为空。
|
||
type ModuleID string
|
||
|
||
// Namespace 返回模块 ID 的命名空间部分(去掉最后一段)。
|
||
//
|
||
// 例如 "gateway.platform.anthropic" 的命名空间为 "gateway.platform";
|
||
// 单段 ID(如 "hello")的命名空间为空字符串。
|
||
func (id ModuleID) Namespace() string {
|
||
lastDot := strings.LastIndex(string(id), ".")
|
||
if lastDot < 0 {
|
||
return ""
|
||
}
|
||
return string(id)[:lastDot]
|
||
}
|
||
|
||
// Name 返回模块 ID 的最后一段,即模块名。
|
||
//
|
||
// 例如 "gateway.platform.anthropic" 的模块名为 "anthropic"。
|
||
func (id ModuleID) Name() string {
|
||
lastDot := strings.LastIndex(string(id), ".")
|
||
if lastDot < 0 {
|
||
return string(id)
|
||
}
|
||
return string(id)[lastDot+1:]
|
||
}
|
||
|
||
// validate 校验模块 ID 格式:非空、点分、各段为非空的 [a-z0-9_-]+。
|
||
// 校验规则有意保持宽松,不做长度等额外限制。
|
||
func (id ModuleID) validate() error {
|
||
if id == "" {
|
||
return fmt.Errorf("module ID is empty")
|
||
}
|
||
for _, segment := range strings.Split(string(id), ".") {
|
||
if segment == "" {
|
||
return fmt.Errorf("module ID %q contains an empty segment", id)
|
||
}
|
||
for _, r := range segment {
|
||
if (r < 'a' || r > 'z') && (r < '0' || r > '9') && r != '_' && r != '-' {
|
||
return fmt.Errorf("module ID %q contains invalid character %q (allowed: [a-z0-9_-] per segment)", id, r)
|
||
}
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ModuleInfo 描述一个模块的注册信息(模块目录中的一条记录)。
|
||
type ModuleInfo struct {
|
||
// ID 是模块的全局唯一标识,见 ModuleID。
|
||
ID ModuleID
|
||
|
||
// New 返回该模块的一个新实例。
|
||
// 每次 Runtime.Build 都会通过 New 创建全新实例,模块不应依赖包级可变状态。
|
||
New func() Module
|
||
|
||
// EnabledByDefault 声明该模块在 `modules:` 配置未显式设置 enabled 时的默认启停状态。
|
||
//
|
||
// 设计说明(默认 enabled 的声明方式):默认值由模块在注册时通过本字段自行声明,
|
||
// 而不是由内核统一约定。这样"缺省配置 = 内置模块全部按各自默认 enabled 值运行"
|
||
// 的语义完全由各模块的注册信息决定;零值 false 意味着新模块默认不启用,
|
||
// 保证引入新模块不会在未配置时改变现有行为(与 Phase-1 零行为变更要求一致)。
|
||
EnabledByDefault bool
|
||
}
|
||
|
||
// Module 是所有插件模块必须实现的最小接口。
|
||
//
|
||
// 模块通常是一个 struct,在所属包的 init() 中调用 RegisterModule 完成自注册。
|
||
// 除 Module 外,模块可按需实现以下可选生命周期接口:
|
||
// Provisioner、Validator、Starter、Stopper。
|
||
type Module interface {
|
||
// ModuleInfo 返回模块的注册信息。该方法必须无副作用且可在零值实例上调用。
|
||
ModuleInfo() ModuleInfo
|
||
}
|
||
|
||
// Provisioner 是可选生命周期接口:在模块实例化后、校验前装配依赖。
|
||
//
|
||
// Provision 在 Runtime.Build 阶段被调用,模块应在此从 Host 获取所需能力
|
||
// (Logger / ConfigOf / DB / Redis)并完成自身初始化。失败将中止启动。
|
||
type Provisioner interface {
|
||
Provision(host *Host) error
|
||
}
|
||
|
||
// Validator 是可选生命周期接口:在 Provision 之后校验模块配置与状态。
|
||
//
|
||
// Validate 在 Runtime.Build 阶段被调用,失败将中止启动。
|
||
type Validator interface {
|
||
Validate() error
|
||
}
|
||
|
||
// Starter 是可选生命周期接口:启动模块的后台工作(goroutine、监听等)。
|
||
//
|
||
// Start 在 Runtime.Start 阶段按注册表稳定序被调用,失败将中止启动并
|
||
// 触发对已启动模块的逆序 Stop 回滚。
|
||
//
|
||
// 契约:实现必须快速返回——长任务应自行启动 goroutine(参照 hello 模块)。
|
||
// Runtime 在持锁状态下调用 Start/Stop,阻塞的实现会同时阻塞 Snapshot
|
||
// (即 admin 模块可观测接口)与其他生命周期操作。
|
||
type Starter interface {
|
||
Start(ctx context.Context) error
|
||
}
|
||
|
||
// Stopper 是可选生命周期接口:停止模块并释放资源。
|
||
//
|
||
// Stop 在 Runtime.Stop 阶段按注册表稳定序的逆序被调用,
|
||
// 实现必须尊重 ctx 的 deadline(与进程优雅关闭超时协同)。
|
||
//
|
||
// 契约:与 Start 相同,实现必须快速返回(等待自身 goroutine 退出时
|
||
// 用 select 配合 ctx.Done(),参照 hello 模块),原因见 Starter 注释。
|
||
type Stopper interface {
|
||
Stop(ctx context.Context) error
|
||
}
|