mosdns/pkg/utils/toposort.go

205 lines
5.1 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 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
}