Initial commit (code only without large binaries)

This commit is contained in:
robin
2026-02-15 18:58:44 +08:00
commit 35df75498f
9442 changed files with 1495866 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
package nodes
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
ClusterId int64
NodeId int64
}) {
// 创建日志
defer this.CreateLogInfo(codes.Node_LogDeleteNodeFromCluster, params.ClusterId, params.NodeId)
_, err := this.RPC().NodeRPC().DeleteNodeFromNodeCluster(this.AdminContext(), &pb.DeleteNodeFromNodeClusterRequest{
NodeId: params.NodeId,
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,12 @@
package nodes
import (
"github.com/iwind/TeaGo/actions"
)
type Helper struct {
}
func (this *Helper) BeforeAction(action *actions.ActionObject) {
action.Data["teaMenu"] = "nodes"
}

View File

@@ -0,0 +1,23 @@
package nodes
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
Helper(new(Helper)).
Prefix("/nodes").
Post("/delete", new(DeleteAction)).
// IP地址
GetPost("/ipAddresses/createPopup", new(ipAddresses.CreatePopupAction)).
GetPost("/ipAddresses/updatePopup", new(ipAddresses.UpdatePopupAction)).
EndAll()
})
}

View File

@@ -0,0 +1,91 @@
package ipAddresses
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"net"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
NodeId int64
SupportThresholds bool
}) {
// 专属集群
clusterMaps, err := ipaddressutils.FindNodeClusterMapsWithNodeId(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["clusters"] = clusterMaps
// 阈值
this.Data["supportThresholds"] = params.SupportThresholds
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
IP string `alias:"ip"`
CanAccess bool
Name string
IsUp bool
ThresholdsJSON []byte
ClusterIds []int64
Must *actions.Must
}) {
params.Must.
Field("ip", params.IP).
Require("请输入IP地址")
result, err := utils.ExtractIP(params.IP)
if err != nil {
this.Fail("IP格式错误'" + params.IP + "'")
}
for _, ip := range result {
if len(net.ParseIP(ip)) == 0 {
this.FailField("ip", "请输入正确的IP")
}
}
// 阈值设置
var thresholds = []*nodeconfigs.IPAddressThresholdConfig{}
if teaconst.IsPlus && len(params.ThresholdsJSON) > 0 {
_ = json.Unmarshal(params.ThresholdsJSON, &thresholds)
}
// 专属集群
// 目前只考虑CDN边缘集群
clusterMaps, err := ipaddressutils.FindNodeClusterMaps(this.Parent(), params.ClusterIds)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["ipAddress"] = maps.Map{
"name": params.Name,
"canAccess": params.CanAccess,
"ip": params.IP,
"id": 0,
"isOn": true,
"isUp": params.IsUp,
"thresholds": thresholds,
"clusters": clusterMaps,
}
this.Success()
}

View File

@@ -0,0 +1,209 @@
package ipaddressutils
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
// UpdateNodeIPAddresses 保存一组IP地址
func UpdateNodeIPAddresses(parentAction *actionutils.ParentAction, nodeId int64, role nodeconfigs.NodeRole, ipAddressesJSON []byte) error {
var addresses = []maps.Map{}
err := json.Unmarshal(ipAddressesJSON, &addresses)
if err != nil {
return err
}
for _, addr := range addresses {
var resultAddrIds = []int64{}
var addrId = addr.GetInt64("id")
// 专属集群
var addrClusterIds = []int64{}
var addrClusters = addr.GetSlice("clusters")
if len(addrClusters) > 0 {
for _, addrCluster := range addrClusters {
var m = maps.NewMap(addrCluster)
var clusterId = m.GetInt64("id")
if clusterId > 0 {
addrClusterIds = append(addrClusterIds, clusterId)
}
}
}
if addrId > 0 {
resultAddrIds = append(resultAddrIds, addrId)
var isOn bool
if !addr.Has("isOn") { // 兼容老版本
isOn = true
} else {
isOn = addr.GetBool("isOn")
}
_, err = parentAction.RPC().NodeIPAddressRPC().UpdateNodeIPAddress(parentAction.AdminContext(), &pb.UpdateNodeIPAddressRequest{
NodeIPAddressId: addrId,
Ip: addr.GetString("ip"),
Name: addr.GetString("name"),
CanAccess: addr.GetBool("canAccess"),
IsOn: isOn,
IsUp: addr.GetBool("isUp"),
ClusterIds: addrClusterIds,
})
if err != nil {
return err
}
} else {
var ipStrings = addr.GetString("ip")
result, _ := utils.ExtractIP(ipStrings)
if len(result) == 1 {
// 单个创建
createResp, err := parentAction.RPC().NodeIPAddressRPC().CreateNodeIPAddress(parentAction.AdminContext(), &pb.CreateNodeIPAddressRequest{
NodeId: nodeId,
Role: role,
Name: addr.GetString("name"),
Ip: result[0],
CanAccess: addr.GetBool("canAccess"),
IsUp: addr.GetBool("isUp"),
NodeClusterIds: addrClusterIds,
})
if err != nil {
return err
}
addrId = createResp.NodeIPAddressId
resultAddrIds = append(resultAddrIds, addrId)
} else if len(result) > 1 {
// 批量创建
createResp, err := parentAction.RPC().NodeIPAddressRPC().CreateNodeIPAddresses(parentAction.AdminContext(), &pb.CreateNodeIPAddressesRequest{
NodeId: nodeId,
Role: role,
Name: addr.GetString("name"),
IpList: result,
CanAccess: addr.GetBool("canAccess"),
IsUp: addr.GetBool("isUp"),
GroupValue: ipStrings,
NodeClusterIds: addrClusterIds,
})
if err != nil {
return err
}
resultAddrIds = append(resultAddrIds, createResp.NodeIPAddressIds...)
}
}
// 保存阈值
var thresholds = addr.GetSlice("thresholds")
if len(thresholds) > 0 {
thresholdsJSON, err := json.Marshal(thresholds)
if err != nil {
return err
}
for _, addrId := range resultAddrIds {
_, err = parentAction.RPC().NodeIPAddressThresholdRPC().UpdateAllNodeIPAddressThresholds(parentAction.AdminContext(), &pb.UpdateAllNodeIPAddressThresholdsRequest{
NodeIPAddressId: addrId,
NodeIPAddressThresholdsJSON: thresholdsJSON,
})
if err != nil {
return err
}
}
} else {
for _, addrId := range resultAddrIds {
_, err = parentAction.RPC().NodeIPAddressThresholdRPC().UpdateAllNodeIPAddressThresholds(parentAction.AdminContext(), &pb.UpdateAllNodeIPAddressThresholdsRequest{
NodeIPAddressId: addrId,
NodeIPAddressThresholdsJSON: []byte("[]"),
})
if err != nil {
return err
}
}
}
}
return nil
}
// InitNodeIPAddressThresholds 初始化IP阈值
func InitNodeIPAddressThresholds(parentAction *actionutils.ParentAction, addrId int64) ([]*nodeconfigs.IPAddressThresholdConfig, error) {
thresholdsResp, err := parentAction.RPC().NodeIPAddressThresholdRPC().FindAllEnabledNodeIPAddressThresholds(parentAction.AdminContext(), &pb.FindAllEnabledNodeIPAddressThresholdsRequest{NodeIPAddressId: addrId})
if err != nil {
return nil, err
}
var thresholds = []*nodeconfigs.IPAddressThresholdConfig{}
if len(thresholdsResp.NodeIPAddressThresholds) > 0 {
for _, pbThreshold := range thresholdsResp.NodeIPAddressThresholds {
var threshold = &nodeconfigs.IPAddressThresholdConfig{
Id: pbThreshold.Id,
Items: []*nodeconfigs.IPAddressThresholdItemConfig{},
Actions: []*nodeconfigs.IPAddressThresholdActionConfig{},
}
if len(pbThreshold.ItemsJSON) > 0 {
err = json.Unmarshal(pbThreshold.ItemsJSON, &threshold.Items)
if err != nil {
return nil, err
}
}
if len(pbThreshold.ActionsJSON) > 0 {
err = json.Unmarshal(pbThreshold.ActionsJSON, &threshold.Actions)
if err != nil {
return nil, err
}
}
thresholds = append(thresholds, threshold)
}
}
return thresholds, nil
}
// FindNodeClusterMapsWithNodeId 根据节点读取集群信息
func FindNodeClusterMapsWithNodeId(parentAction *actionutils.ParentAction, nodeId int64) ([]maps.Map, error) {
var clusterMaps = []maps.Map{}
if nodeId > 0 { // CDN边缘节点
nodeResp, err := parentAction.RPC().NodeRPC().FindEnabledNode(parentAction.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: nodeId})
if err != nil {
return nil, err
}
var node = nodeResp.Node
if node != nil {
var clusters = []*pb.NodeCluster{}
if node.NodeCluster != nil {
clusters = append(clusters, nodeResp.Node.NodeCluster)
}
if len(node.SecondaryNodeClusters) > 0 {
clusters = append(clusters, node.SecondaryNodeClusters...)
}
for _, cluster := range clusters {
clusterMaps = append(clusterMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"isChecked": false,
})
}
}
}
return clusterMaps, nil
}
// FindNodeClusterMaps 根据一组集群ID读取集群信息
func FindNodeClusterMaps(parentAction *actionutils.ParentAction, clusterIds []int64) ([]maps.Map, error) {
var clusterMaps = []maps.Map{}
if len(clusterIds) > 0 {
for _, clusterId := range clusterIds {
clusterResp, err := parentAction.RPC().NodeClusterRPC().FindEnabledNodeCluster(parentAction.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: clusterId})
if err != nil {
return nil, err
}
var cluster = clusterResp.NodeCluster
if cluster != nil {
clusterMaps = append(clusterMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
})
}
}
}
return clusterMaps, nil
}

View File

@@ -0,0 +1,115 @@
package ipAddresses
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"net"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
NodeId int64
AddressId int64
SupportThresholds bool
}) {
// 专属集群
clusterMaps, err := ipaddressutils.FindNodeClusterMapsWithNodeId(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["clusters"] = clusterMaps
this.Data["supportThresholds"] = params.SupportThresholds
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
AddressId int64
IP string `alias:"ip"`
Name string
CanAccess bool
IsOn bool
IsUp bool
ThresholdsJSON []byte
ClusterIds []int64
Must *actions.Must
}) {
params.Must.
Field("ip", params.IP).
Require("请输入IP地址")
// 获取IP地址信息
var isUp = params.IsUp
if params.AddressId > 0 {
addressResp, err := this.RPC().NodeIPAddressRPC().FindEnabledNodeIPAddress(this.AdminContext(), &pb.FindEnabledNodeIPAddressRequest{NodeIPAddressId: params.AddressId})
if err != nil {
this.ErrorPage(err)
return
}
var address = addressResp.NodeIPAddress
if address == nil {
this.Fail("找不到要修改的地址")
}
}
if params.AddressId > 0 {
ip := net.ParseIP(params.IP)
if len(ip) == 0 {
this.Fail("请输入正确的IP")
}
} else {
result, err := utils.ExtractIP(params.IP)
if err != nil {
this.Fail("IP格式错误'" + params.IP + "'")
}
for _, ip := range result {
if len(net.ParseIP(ip)) == 0 {
this.FailField("ip", "请输入正确的IP")
}
}
}
var thresholds = []*nodeconfigs.IPAddressThresholdConfig{}
if teaconst.IsPlus && len(params.ThresholdsJSON) > 0 {
_ = json.Unmarshal(params.ThresholdsJSON, &thresholds)
}
// 专属集群
// 目前只考虑CDN边缘集群
clusterMaps, err := ipaddressutils.FindNodeClusterMaps(this.Parent(), params.ClusterIds)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["ipAddress"] = maps.Map{
"name": params.Name,
"ip": params.IP,
"id": params.AddressId,
"canAccess": params.CanAccess,
"isOn": params.IsOn,
"isUp": isUp,
"thresholds": thresholds,
"clusters": clusterMaps,
}
this.Success()
}

View File

@@ -0,0 +1,371 @@
package nodeutils
import (
"context"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"sort"
"strconv"
"sync"
)
// MessageResult 和节点消息通讯结果定义
type MessageResult struct {
NodeId int64 `json:"nodeId"`
NodeName string `json:"nodeName"`
IsOK bool `json:"isOk"`
Message string `json:"message"`
}
// SendMessageToCluster 向集群发送命令消息
func SendMessageToCluster(ctx context.Context, clusterId int64, code string, msg any, timeoutSeconds int32, availableNodesOnly bool) (results []*MessageResult, err error) {
results = []*MessageResult{}
msgJSON, err := json.Marshal(msg)
if err != nil {
return results, err
}
defaultRPCClient, err := rpc.SharedRPC()
if err != nil {
return results, err
}
// 获取所有节点
nodesResp, err := defaultRPCClient.NodeRPC().FindAllEnabledNodesWithNodeClusterId(ctx, &pb.FindAllEnabledNodesWithNodeClusterIdRequest{
NodeClusterId: clusterId,
IncludeSecondary: true,
})
if err != nil {
return results, err
}
var nodes = nodesResp.Nodes
if len(nodes) == 0 {
return results, nil
}
if availableNodesOnly {
var newNodes []*pb.Node
for _, node := range nodes {
if !node.IsOn {
continue
}
newNodes = append(newNodes, node)
}
nodes = newNodes
}
var rpcMap = map[int64]*rpc.RPCClient{} // apiNodeId => RPCClient
var locker = &sync.Mutex{}
var wg = &sync.WaitGroup{}
wg.Add(len(nodes))
for _, node := range nodes {
// TODO 检查是否在线
if !node.IsOn {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "节点尚未启用",
})
locker.Unlock()
wg.Done()
continue
}
if len(node.ConnectedAPINodeIds) == 0 {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "节点尚未连接到API",
})
locker.Unlock()
wg.Done()
continue
}
// 获取API节点信息
apiNodeId := node.ConnectedAPINodeIds[0]
rpcClient, ok := rpcMap[apiNodeId]
if !ok {
apiNodeResp, err := defaultRPCClient.APINodeRPC().FindEnabledAPINode(ctx, &pb.FindEnabledAPINodeRequest{ApiNodeId: apiNodeId})
if err != nil {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "无法读取对应的API节点信息" + err.Error(),
})
locker.Unlock()
wg.Done()
continue
}
if apiNodeResp.ApiNode == nil {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "无法读取对应的API节点信息API节点ID" + strconv.FormatInt(apiNodeId, 10),
})
locker.Unlock()
wg.Done()
continue
}
apiNode := apiNodeResp.ApiNode
apiRPCClient, err := rpc.NewRPCClient(&configs.APIConfig{
RPCEndpoints: apiNode.AccessAddrs,
NodeId: apiNode.UniqueId,
Secret: apiNode.Secret,
}, false)
if err != nil {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "初始化API节点错误API节点ID" + strconv.FormatInt(apiNodeId, 10) + "" + err.Error(),
})
locker.Unlock()
wg.Done()
continue
}
rpcMap[apiNodeId] = apiRPCClient
rpcClient = apiRPCClient
}
// 发送消息
go func(node *pb.Node) {
defer wg.Done()
result, err := rpcClient.NodeRPC().SendCommandToNode(ctx, &pb.NodeStreamMessage{
NodeId: node.Id,
TimeoutSeconds: timeoutSeconds,
Code: code,
DataJSON: msgJSON,
})
if err != nil {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "API返回错误" + err.Error(),
})
locker.Unlock()
return
}
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: result.IsOk,
Message: result.Message,
})
locker.Unlock()
}(node)
}
wg.Wait()
// 对结果进行排序
if len(results) > 0 {
sort.Slice(results, func(i, j int) bool {
return results[i].NodeId < results[j].NodeId
})
}
// 关闭RPC
for _, rpcClient := range rpcMap {
_ = rpcClient.Close()
}
return
}
// SendMessageToNodeIds 向一组节点发送命令消息
func SendMessageToNodeIds(ctx context.Context, nodeIds []int64, code string, msg interface{}, timeoutSeconds int32) (results []*MessageResult, err error) {
results = []*MessageResult{}
if len(nodeIds) == 0 {
return
}
msgJSON, err := json.Marshal(msg)
if err != nil {
return results, err
}
defaultRPCClient, err := rpc.SharedRPC()
if err != nil {
return results, err
}
// 获取所有节点
nodesResp, err := defaultRPCClient.NodeRPC().FindEnabledNodesWithIds(ctx, &pb.FindEnabledNodesWithIdsRequest{NodeIds: nodeIds})
if err != nil {
return nil, err
}
nodes := nodesResp.Nodes
if len(nodes) == 0 {
return results, nil
}
rpcMap := map[int64]*rpc.RPCClient{} // apiNodeId => RPCClient
locker := &sync.Mutex{}
wg := &sync.WaitGroup{}
wg.Add(len(nodes))
for _, node := range nodes {
if !node.IsActive {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "节点不在线",
})
locker.Unlock()
wg.Done()
continue
}
if !node.IsOn {
if !node.IsActive {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "节点未启用",
})
locker.Unlock()
wg.Done()
continue
}
}
if len(node.ConnectedAPINodeIds) == 0 {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "节点尚未连接到API",
})
locker.Unlock()
wg.Done()
continue
}
// 获取API节点信息
apiNodeId := node.ConnectedAPINodeIds[0]
rpcClient, ok := rpcMap[apiNodeId]
if !ok {
apiNodeResp, err := defaultRPCClient.APINodeRPC().FindEnabledAPINode(ctx, &pb.FindEnabledAPINodeRequest{ApiNodeId: apiNodeId})
if err != nil {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "无法读取对应的API节点信息" + err.Error(),
})
locker.Unlock()
wg.Done()
continue
}
if apiNodeResp.ApiNode == nil {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "无法读取对应的API节点信息API节点ID" + strconv.FormatInt(apiNodeId, 10),
})
locker.Unlock()
wg.Done()
continue
}
apiNode := apiNodeResp.ApiNode
apiRPCClient, err := rpc.NewRPCClient(&configs.APIConfig{
RPCEndpoints: apiNode.AccessAddrs,
NodeId: apiNode.UniqueId,
Secret: apiNode.Secret,
}, false)
if err != nil {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "初始化API节点错误API节点ID" + strconv.FormatInt(apiNodeId, 10) + "" + err.Error(),
})
locker.Unlock()
wg.Done()
continue
}
rpcMap[apiNodeId] = apiRPCClient
rpcClient = apiRPCClient
}
// 发送消息
go func(node *pb.Node) {
defer wg.Done()
result, err := rpcClient.NodeRPC().SendCommandToNode(ctx, &pb.NodeStreamMessage{
NodeId: node.Id,
TimeoutSeconds: timeoutSeconds,
Code: code,
DataJSON: msgJSON,
})
if err != nil {
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: false,
Message: "API返回错误" + err.Error(),
})
locker.Unlock()
return
}
locker.Lock()
results = append(results, &MessageResult{
NodeId: node.Id,
NodeName: node.Name,
IsOK: result.IsOk,
Message: result.Message,
})
locker.Unlock()
}(node)
}
wg.Wait()
// 对结果进行排序
if len(results) > 0 {
sort.Slice(results, func(i, j int) bool {
return results[i].NodeId < results[j].NodeId
})
}
// 关闭RPC
for _, rpcClient := range rpcMap {
_ = rpcClient.Close()
}
return
}

View File

@@ -0,0 +1,22 @@
package nodeutils
import (
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/logs"
"testing"
)
func TestSendMessageToCluster(t *testing.T) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
t.Fatal(err)
}
ctx := rpcClient.Context(1)
results, err := SendMessageToCluster(ctx, 1, "test", nil, 30, false)
if err != nil {
t.Fatal(err)
}
logs.PrintAsJSON(results, t)
}