205 lines
5.1 KiB
Go
205 lines
5.1 KiB
Go
package utils
|
||
|
||
import (
|
||
"fmt"
|
||
)
|
||
|
||
// PluginConfig 插件配置结构
|
||
type PluginConfig struct {
|
||
Tag string
|
||
Type string
|
||
Args any
|
||
}
|
||
|
||
// PluginNode 表示插件节点及其依赖关系
|
||
type PluginNode struct {
|
||
Index int // 在原始数组中的索引
|
||
Config PluginConfig // 插件配置
|
||
Deps []string // 依赖的插件标签
|
||
}
|
||
|
||
// TopologicalSort 对插件配置进行拓扑排序
|
||
// 返回排序后的插件配置列表,如果存在循环依赖则返回错误
|
||
func TopologicalSort(plugins []PluginConfig) ([]PluginConfig, error) {
|
||
// 构建依赖图:graph[A] = [B, C] 表示 A 依赖 B 和 C
|
||
graph := buildDependencyGraph(plugins)
|
||
|
||
// 反转图:reversedGraph[B] = [A] 表示 B 被 A 依赖
|
||
// 这样计算入度时更直观
|
||
reversedGraph := make(map[string][]string)
|
||
allNodes := make(map[string]bool)
|
||
|
||
for node := range graph {
|
||
allNodes[node] = true
|
||
for _, dep := range graph[node] {
|
||
allNodes[dep] = true
|
||
reversedGraph[dep] = append(reversedGraph[dep], node)
|
||
}
|
||
}
|
||
|
||
// 计算入度(依赖的数量)
|
||
inDegree := make(map[string]int)
|
||
for node := range allNodes {
|
||
inDegree[node] = len(graph[node])
|
||
}
|
||
|
||
// 找出所有入度为0的节点(不依赖任何其他节点)
|
||
queue := []string{}
|
||
for node, degree := range inDegree {
|
||
if degree == 0 {
|
||
queue = append(queue, node)
|
||
}
|
||
}
|
||
|
||
// BFS遍历
|
||
result := []PluginConfig{}
|
||
for len(queue) > 0 {
|
||
node := queue[0]
|
||
queue = queue[1:]
|
||
result = append(result, findPluginConfigByTag(plugins, node))
|
||
|
||
// 对于所有依赖当前节点的节点,减少它们的入度
|
||
for _, dependent := range reversedGraph[node] {
|
||
inDegree[dependent]--
|
||
if inDegree[dependent] == 0 {
|
||
queue = append(queue, dependent)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检测循环依赖
|
||
if len(result) != len(plugins) {
|
||
return nil, fmt.Errorf("检测到循环依赖,无法进行拓扑排序")
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// buildDependencyGraph 构建插件依赖图
|
||
func buildDependencyGraph(plugins []PluginConfig) map[string][]string {
|
||
graph := make(map[string][]string)
|
||
|
||
for _, p := range plugins {
|
||
graph[p.Tag] = extractDependencies(p)
|
||
}
|
||
|
||
return graph
|
||
}
|
||
|
||
// extractDependencies 从插件配置中提取依赖关系
|
||
// 解析配置中的 $plugin_name 引用
|
||
func extractDependencies(config PluginConfig) []string {
|
||
var deps []string
|
||
|
||
// 将配置转换为字符串进行正则匹配
|
||
configStr := fmt.Sprintf("%+v", config.Args)
|
||
|
||
// 调试:打印配置字符串
|
||
// fmt.Printf("DEBUG: Plugin %s, configStr: %s\n", config.Tag, configStr)
|
||
|
||
// 1. 查找 $xxx 格式的引用
|
||
i := 0
|
||
for i < len(configStr) {
|
||
if i+1 < len(configStr) && configStr[i] == '$' {
|
||
// 找到变量名的开始
|
||
start := i + 1
|
||
// 查找变量名的结束(遇到非字母数字下划线字符)
|
||
end := start
|
||
for end < len(configStr) && (isAlphaNumeric(configStr[end]) || configStr[end] == '_') {
|
||
end++
|
||
}
|
||
|
||
if start < end {
|
||
dep := configStr[start:end]
|
||
// 排除一些常见的关键字,避免误识别
|
||
if dep != "primary" && dep != "secondary" && dep != "timeout" && dep != "china_ip" {
|
||
deps = append(deps, dep)
|
||
}
|
||
}
|
||
i = end
|
||
} else {
|
||
i++
|
||
}
|
||
}
|
||
|
||
// 2. 特殊处理:检查 entry 字段(用于 server 插件)
|
||
// 服务器插件使用 "entry: plugin_name" 而不是 "$plugin_name"
|
||
// 搜索配置字符串中的 "entry:" 模式
|
||
entryPrefix := "entry:"
|
||
entryIdx := 0
|
||
for {
|
||
idx := stringIndexFrom(configStr, entryPrefix, entryIdx)
|
||
if idx == -1 {
|
||
break
|
||
}
|
||
|
||
// 找到 entry: 后面的值
|
||
start := idx + len(entryPrefix)
|
||
// 跳过空格
|
||
for start < len(configStr) && configStr[start] == ' ' {
|
||
start++
|
||
}
|
||
|
||
// 提取 entry 值(直到遇到空格或特殊字符)
|
||
end := start
|
||
for end < len(configStr) && isAlphaNumeric(configStr[end]) {
|
||
end++
|
||
}
|
||
|
||
if start < end {
|
||
entryValue := configStr[start:end]
|
||
deps = append(deps, entryValue)
|
||
// fmt.Printf("DEBUG: Found entry dependency for %s: %s\n", config.Tag, entryValue)
|
||
}
|
||
|
||
entryIdx = end
|
||
}
|
||
|
||
// 调试:打印最终依赖列表
|
||
// if len(deps) > 0 {
|
||
// fmt.Printf("DEBUG: Plugin %s depends on: %v\n", config.Tag, deps)
|
||
// }
|
||
|
||
return deps
|
||
}
|
||
|
||
// stringIndexFrom 从指定位置开始查找子串
|
||
func stringIndexFrom(s, substr string, from int) int {
|
||
if from >= len(s) {
|
||
return -1
|
||
}
|
||
for i := from; i < len(s)-len(substr)+1; i++ {
|
||
if s[i:i+len(substr)] == substr {
|
||
return i
|
||
}
|
||
}
|
||
return -1
|
||
}
|
||
|
||
// isAlphaNumeric 判断字符是否为字母、数字或下划线
|
||
func isAlphaNumeric(c byte) bool {
|
||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'
|
||
}
|
||
|
||
// findPluginConfigByTag 根据标签查找插件配置
|
||
func findPluginConfigByTag(plugins []PluginConfig, tag string) PluginConfig {
|
||
for _, p := range plugins {
|
||
if p.Tag == tag {
|
||
return p
|
||
}
|
||
}
|
||
// 理论上不会出现找不到的情况,因为我们是从现有标签构建的图
|
||
return PluginConfig{}
|
||
}
|
||
|
||
// ValidateConfigOrder 验证配置顺序是否正确
|
||
// 如果配置顺序有问题,返回建议的正确顺序
|
||
func ValidateConfigOrder(plugins []PluginConfig) ([]PluginConfig, error) {
|
||
sorted, err := TopologicalSort(plugins)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return sorted, nil
|
||
}
|