Files
shaw 8ba2fbd786 feat(plugin): Phase-1/1.5 进程内插件内核 + 开发套件后端
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)与回归测试。
2026-06-11 22:58:51 +08:00

134 lines
5.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}