Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions server/api/v1/enter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/api/v1/example"
"github.com/flipped-aurora/gin-vue-admin/server/api/v1/imageregistry"
"github.com/flipped-aurora/gin-vue-admin/server/api/v1/instance"
"github.com/flipped-aurora/gin-vue-admin/server/api/v1/pcdn"
"github.com/flipped-aurora/gin-vue-admin/server/api/v1/product"
"github.com/flipped-aurora/gin-vue-admin/server/api/v1/system"
)
Expand All @@ -18,4 +19,5 @@ type ApiGroup struct {
ComputenodeApiGroup computenode.ApiGroup
ProductApiGroup product.ApiGroup
InstanceApiGroup instance.ApiGroup
PcdnApiGroup pcdn.ApiGroup
}
10 changes: 10 additions & 0 deletions server/api/v1/pcdn/enter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package pcdn

import "github.com/flipped-aurora/gin-vue-admin/server/service"

type ApiGroup struct{ PcdnMetricsApi }

var (
pcdnMetricsService = service.ServiceGroupApp.PcdnServiceGroup.MetricsService
healthScoreService = service.ServiceGroupApp.PcdnServiceGroup.HealthScoreService
)
69 changes: 69 additions & 0 deletions server/api/v1/pcdn/pcdn_metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package pcdn

import (
"errors"

"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
pcdnReq "github.com/flipped-aurora/gin-vue-admin/server/model/pcdn/request"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/gorm"
)

type PcdnMetricsApi struct{}

func (p *PcdnMetricsApi) ReportMetrics(c *gin.Context) {
ctx := c.Request.Context()
var req pcdnReq.PcdnMetricsReport
if err := c.ShouldBindJSON(&req); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if nodeIDFromToken, ok := c.Get("pcdnNodeID"); ok {
if id, ok := nodeIDFromToken.(uint); ok && id != req.NodeID {
response.FailWithMessage("节点身份与上报节点不匹配", c)
return
}
}

snapshot, weight, err := pcdnMetricsService.ReportMetrics(ctx, req)
if err != nil {
global.GVA_LOG.Error("上报PCDN指标失败", zap.Error(err), zap.Uint("nodeId", req.NodeID))
response.FailWithMessage("上报失败:"+err.Error(), c)
return
}

response.OkWithDetailed(gin.H{
"snapshot": snapshot,
"schedulerWeight": weight,
}, "上报成功", c)
}

func (p *PcdnMetricsApi) GetLatestSnapshot(c *gin.Context) {
ctx := c.Request.Context()
var req struct {
NodeID uint `form:"nodeId" binding:"required"`
}
if err := c.ShouldBindQuery(&req); err != nil {
response.FailWithMessage(err.Error(), c)
return
}

snapshot, err := pcdnMetricsService.GetLatestSnapshot(ctx, req.NodeID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
response.FailWithMessage("节点暂无指标快照", c)
return
}
global.GVA_LOG.Error("查询PCDN指标失败", zap.Error(err), zap.Uint("nodeId", req.NodeID))
response.FailWithMessage("查询失败:"+err.Error(), c)
return
}

weight := healthScoreService.SchedulerWeight(snapshot.Window1mScore, snapshot.Window5mScore, snapshot.Window15mScore)
response.OkWithData(gin.H{
"snapshot": snapshot,
"schedulerWeight": weight,
}, c)
}
3 changes: 2 additions & 1 deletion server/initialize/gorm_biz.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/model/computenode"
"github.com/flipped-aurora/gin-vue-admin/server/model/imageregistry"
"github.com/flipped-aurora/gin-vue-admin/server/model/instance"
"github.com/flipped-aurora/gin-vue-admin/server/model/pcdn"
"github.com/flipped-aurora/gin-vue-admin/server/model/product"
)

func bizModel() error {
db := global.GVA_DB
err := db.AutoMigrate(imageregistry.ImageRegistry{}, computenode.ComputeNode{}, product.ProductSpec{}, instance.Instance{})
err := db.AutoMigrate(imageregistry.ImageRegistry{}, computenode.ComputeNode{}, product.ProductSpec{}, instance.Instance{}, pcdn.PcdnMetricSnapshot{})
if err != nil {
return err
}
Expand Down
5 changes: 5 additions & 0 deletions server/initialize/router_biz.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ func initBizRouter(routers ...*gin.RouterGroup) {
productRouter := router.RouterGroupApp.Product
productRouter.InitProductSpecRouter(privateGroup, publicGroup)
} // 占位方法,保证文件可以正确加载,避免go空变量检测报错,请勿删除。

{
instanceRouter := router.RouterGroupApp.Instance
instanceRouter.InitInstanceRouter(privateGroup, publicGroup)
}
{
pcdnRouter := router.RouterGroupApp.Pcdn
pcdnRouter.InitPcdnMetricsRouter(privateGroup, publicGroup)
}
}
73 changes: 73 additions & 0 deletions server/middleware/pcdn_node_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package middleware

import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"fmt"
"strconv"
"strings"
"time"

"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/computenode"
"github.com/gin-gonic/gin"
)

// PcdnNodeAuth 通过节点密钥签名校验上报请求,避免伪造上报。
// Header:
//
// X-Node-Id: 节点ID
// X-Node-Timestamp: Unix秒时间戳
// X-Node-Signature: hex(HMAC-SHA256("{nodeId}:{timestamp}", node.password))
func PcdnNodeAuth() gin.HandlerFunc {
return func(c *gin.Context) {
nodeIDHeader := c.GetHeader("X-Node-Id")
tsHeader := c.GetHeader("X-Node-Timestamp")
signatureHeader := strings.ToLower(c.GetHeader("X-Node-Signature"))
if nodeIDHeader == "" || tsHeader == "" || signatureHeader == "" {
c.AbortWithStatusJSON(401, gin.H{"msg": "缺少节点鉴权头"})
return
}

nodeID64, err := strconv.ParseUint(nodeIDHeader, 10, 64)
if err != nil {
c.AbortWithStatusJSON(401, gin.H{"msg": "无效节点ID"})
return
}
ts, err := strconv.ParseInt(tsHeader, 10, 64)
if err != nil {
c.AbortWithStatusJSON(401, gin.H{"msg": "无效时间戳"})
return
}

now := time.Now().Unix()
if ts < now-300 || ts > now+60 {
c.AbortWithStatusJSON(401, gin.H{"msg": "签名已过期"})
return
}

var node computenode.ComputeNode
if err = global.GVA_DB.Where("id = ?", uint(nodeID64)).First(&node).Error; err != nil {
c.AbortWithStatusJSON(401, gin.H{"msg": "节点不存在"})
return
}
if node.Password == nil || *node.Password == "" {
c.AbortWithStatusJSON(401, gin.H{"msg": "节点未配置上报密钥"})
return
}

mac := hmac.New(sha256.New, []byte(*node.Password))
mac.Write([]byte(fmt.Sprintf("%d:%d", nodeID64, ts)))
expectedSignature := hex.EncodeToString(mac.Sum(nil))

if subtle.ConstantTimeCompare([]byte(signatureHeader), []byte(expectedSignature)) != 1 {
c.AbortWithStatusJSON(401, gin.H{"msg": "签名校验失败"})
return
}

c.Set("pcdnNodeID", uint(nodeID64))
c.Next()
}
}
27 changes: 27 additions & 0 deletions server/model/pcdn/pcdn_metric_snapshot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package pcdn

import (
"time"

"github.com/flipped-aurora/gin-vue-admin/server/global"
)

// PcdnMetricSnapshot PCDN 节点周期指标快照
type PcdnMetricSnapshot struct {
global.GVA_MODEL
NodeID uint `json:"nodeId" gorm:"comment:节点ID;column:node_id;index:idx_node_reported_at,priority:1"`
ReportedAt time.Time `json:"reportedAt" gorm:"comment:节点上报时间;column:reported_at;index:idx_node_reported_at,priority:2"`
RTTMs float64 `json:"rttMs" gorm:"comment:RTT毫秒;column:rtt_ms;type:decimal(10,3)"`
PacketLossRate float64 `json:"packetLossRate" gorm:"comment:丢包率(0~1);column:packet_loss_rate;type:decimal(10,6)"`
BandwidthUtilization float64 `json:"bandwidthUtilization" gorm:"comment:带宽占用率(0~1);column:bandwidth_utilization;type:decimal(10,6)"`
CacheHitRate float64 `json:"cacheHitRate" gorm:"comment:缓存命中率(0~1);column:cache_hit_rate;type:decimal(10,6)"`
ActiveConnections int64 `json:"activeConnections" gorm:"comment:活跃连接数;column:active_connections"`
HealthScore float64 `json:"healthScore" gorm:"comment:健康分(0~100);column:health_score;type:decimal(10,3)"`
Window1mScore float64 `json:"window1mScore" gorm:"comment:1分钟窗口健康分;column:window_1m_score;type:decimal(10,3)"`
Window5mScore float64 `json:"window5mScore" gorm:"comment:5分钟窗口健康分;column:window_5m_score;type:decimal(10,3)"`
Window15mScore float64 `json:"window15mScore" gorm:"comment:15分钟窗口健康分;column:window_15m_score;type:decimal(10,3)"`
}

func (PcdnMetricSnapshot) TableName() string {
return "pcdn_metric_snapshot"
}
13 changes: 13 additions & 0 deletions server/model/pcdn/request/pcdn_metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package request

import "time"

type PcdnMetricsReport struct {
NodeID uint `json:"nodeId" binding:"required"`
ReportedAt time.Time `json:"reportedAt"`
RTTMs float64 `json:"rttMs" binding:"required,gte=0"`
PacketLossRate float64 `json:"packetLossRate" binding:"required,gte=0,lte=1"`
BandwidthUtilization float64 `json:"bandwidthUtilization" binding:"required,gte=0,lte=1"`
CacheHitRate float64 `json:"cacheHitRate" binding:"required,gte=0,lte=1"`
ActiveConnections int64 `json:"activeConnections" binding:"required,gte=0"`
}
2 changes: 2 additions & 0 deletions server/router/enter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/router/example"
"github.com/flipped-aurora/gin-vue-admin/server/router/imageregistry"
"github.com/flipped-aurora/gin-vue-admin/server/router/instance"
"github.com/flipped-aurora/gin-vue-admin/server/router/pcdn"
"github.com/flipped-aurora/gin-vue-admin/server/router/product"
"github.com/flipped-aurora/gin-vue-admin/server/router/system"
)
Expand All @@ -18,4 +19,5 @@ type RouterGroup struct {
Computenode computenode.RouterGroup
Product product.RouterGroup
Instance instance.RouterGroup
Pcdn pcdn.RouterGroup
}
7 changes: 7 additions & 0 deletions server/router/pcdn/enter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pcdn

import api "github.com/flipped-aurora/gin-vue-admin/server/api/v1"

type RouterGroup struct{ PcdnMetricsRouter }

var pcdnMetricsApi = api.ApiGroupApp.PcdnApiGroup.PcdnMetricsApi
19 changes: 19 additions & 0 deletions server/router/pcdn/pcdn_metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package pcdn

import (
"github.com/flipped-aurora/gin-vue-admin/server/middleware"
"github.com/gin-gonic/gin"
)

type PcdnMetricsRouter struct{}

func (r *PcdnMetricsRouter) InitPcdnMetricsRouter(privateRouter *gin.RouterGroup, publicRouter *gin.RouterGroup) {
pcdnPrivate := privateRouter.Group("pcdnMetrics")
pcdnPublic := publicRouter.Group("pcdnMetrics").Use(middleware.PcdnNodeAuth())
{
pcdnPublic.POST("report", pcdnMetricsApi.ReportMetrics)
}
{
pcdnPrivate.GET("latest", pcdnMetricsApi.GetLatestSnapshot)
}
}
2 changes: 2 additions & 0 deletions server/service/enter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/service/example"
"github.com/flipped-aurora/gin-vue-admin/server/service/imageregistry"
"github.com/flipped-aurora/gin-vue-admin/server/service/instance"
"github.com/flipped-aurora/gin-vue-admin/server/service/pcdn"
"github.com/flipped-aurora/gin-vue-admin/server/service/product"
"github.com/flipped-aurora/gin-vue-admin/server/service/system"
)
Expand All @@ -18,4 +19,5 @@ type ServiceGroup struct {
ComputenodeServiceGroup computenode.ServiceGroup
ProductServiceGroup product.ServiceGroup
InstanceServiceGroup instance.ServiceGroup
PcdnServiceGroup pcdn.ServiceGroup
}
6 changes: 6 additions & 0 deletions server/service/pcdn/enter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package pcdn

type ServiceGroup struct {
MetricsService
HealthScoreService
}
Loading
Loading